diff options
Diffstat (limited to 'scene')
333 files changed, 27674 insertions, 16676 deletions
diff --git a/scene/2d/animated_sprite_2d.cpp b/scene/2d/animated_sprite_2d.cpp index 9ee37670d1..96a3134691 100644 --- a/scene/2d/animated_sprite_2d.cpp +++ b/scene/2d/animated_sprite_2d.cpp @@ -30,7 +30,6 @@ #include "animated_sprite_2d.h" -#include "core/os/os.h" #include "scene/main/viewport.h" #include "scene/scene_string_names.h" @@ -123,7 +122,7 @@ void AnimatedSprite2D::_validate_property(PropertyInfo &property) const { } property.hint_string += String(E->get()); - if (animation == E->get()) { + if (animation == E) { current_found = true; } } @@ -159,12 +158,12 @@ void AnimatedSprite2D::_notification(int p_what) { return; } - float speed = frames->get_animation_speed(animation) * speed_scale; + double speed = frames->get_animation_speed(animation) * speed_scale; if (speed == 0) { return; //do nothing } - float remaining = get_process_delta_time(); + double remaining = get_process_delta_time(); while (remaining) { if (timeout <= 0) { @@ -205,7 +204,7 @@ void AnimatedSprite2D::_notification(int p_what) { emit_signal(SceneStringNames::get_singleton()->frame_changed); } - float to_process = MIN(timeout, remaining); + double to_process = MIN(timeout, remaining); remaining -= to_process; timeout -= to_process; } @@ -310,8 +309,8 @@ int AnimatedSprite2D::get_frame() const { return frame; } -void AnimatedSprite2D::set_speed_scale(float p_speed_scale) { - float elapsed = _get_frame_duration() - timeout; +void AnimatedSprite2D::set_speed_scale(double p_speed_scale) { + double elapsed = _get_frame_duration() - timeout; speed_scale = MAX(p_speed_scale, 0.0f); @@ -320,7 +319,7 @@ void AnimatedSprite2D::set_speed_scale(float p_speed_scale) { timeout -= elapsed; } -float AnimatedSprite2D::get_speed_scale() const { +double AnimatedSprite2D::get_speed_scale() const { return speed_scale; } @@ -402,9 +401,9 @@ bool AnimatedSprite2D::is_playing() const { return playing; } -float AnimatedSprite2D::_get_frame_duration() { +double AnimatedSprite2D::_get_frame_duration() { if (frames.is_valid() && frames->has_animation(animation)) { - float speed = frames->get_animation_speed(animation) * speed_scale; + double speed = frames->get_animation_speed(animation) * speed_scale; if (speed > 0) { return 1.0 / speed; } diff --git a/scene/2d/animated_sprite_2d.h b/scene/2d/animated_sprite_2d.h index ef0027edf1..ac4b20a6d9 100644 --- a/scene/2d/animated_sprite_2d.h +++ b/scene/2d/animated_sprite_2d.h @@ -33,7 +33,6 @@ #include "scene/2d/node_2d.h" #include "scene/resources/sprite_frames.h" -#include "scene/resources/texture.h" class AnimatedSprite2D : public Node2D { GDCLASS(AnimatedSprite2D, Node2D); @@ -56,7 +55,7 @@ class AnimatedSprite2D : public Node2D { void _res_changed(); - float _get_frame_duration(); + double _get_frame_duration(); void _reset_timeout(); void _set_playing(bool p_playing); bool _is_playing() const; @@ -94,8 +93,8 @@ public: void set_frame(int p_frame); int get_frame() const; - void set_speed_scale(float p_speed_scale); - float get_speed_scale() const; + void set_speed_scale(double p_speed_scale); + double get_speed_scale() const; void set_centered(bool p_center); bool is_centered() const; diff --git a/scene/2d/area_2d.cpp b/scene/2d/area_2d.cpp index cf84767151..d78b9847e6 100644 --- a/scene/2d/area_2d.cpp +++ b/scene/2d/area_2d.cpp @@ -32,7 +32,6 @@ #include "scene/scene_string_names.h" #include "servers/audio_server.h" -#include "servers/physics_server_2d.h" void Area2D::set_space_override_mode(SpaceOverride p_mode) { space_override = p_mode; diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp index 860ccfec64..8401909384 100644 --- a/scene/2d/audio_stream_player_2d.cpp +++ b/scene/2d/audio_stream_player_2d.cpp @@ -30,129 +30,21 @@ #include "audio_stream_player_2d.h" -#include "core/config/engine.h" #include "scene/2d/area_2d.h" +#include "scene/2d/listener_2d.h" #include "scene/main/window.h" -void AudioStreamPlayer2D::_mix_audio() { - if (!stream_playback.is_valid() || !active.is_set() || - (stream_paused && !stream_paused_fade_out)) { - return; - } - - if (setseek.get() >= 0.0) { - stream_playback->start(setseek.get()); - setseek.set(-1.0); //reset seek - } - - //get data - AudioFrame *buffer = mix_buffer.ptrw(); - int buffer_size = mix_buffer.size(); - - if (stream_paused_fade_out) { - // Short fadeout ramp - buffer_size = MIN(buffer_size, 128); - } - - stream_playback->mix(buffer, pitch_scale, buffer_size); - - //write all outputs - int oc = output_count.get(); - for (int i = 0; i < oc; i++) { - Output current = outputs[i]; - - //see if current output exists, to keep volume ramp - bool found = false; - for (int j = i; j < prev_output_count; j++) { - if (prev_outputs[j].viewport == current.viewport) { - if (j != i) { - SWAP(prev_outputs[j], prev_outputs[i]); - } - found = true; - break; - } - } - - if (!found) { - //create new if was not used before - if (prev_output_count < MAX_OUTPUTS) { - prev_outputs[prev_output_count] = prev_outputs[i]; //may be owned by another viewport - prev_output_count++; - } - prev_outputs[i] = current; - } - - //mix! - AudioFrame target_volume = stream_paused_fade_out ? AudioFrame(0.f, 0.f) : current.vol; - AudioFrame vol_prev = stream_paused_fade_in ? AudioFrame(0.f, 0.f) : prev_outputs[i].vol; - AudioFrame vol_inc = (target_volume - vol_prev) / float(buffer_size); - AudioFrame vol = vol_prev; - - int cc = AudioServer::get_singleton()->get_channel_count(); - - if (cc == 1) { - if (!AudioServer::get_singleton()->thread_has_channel_mix_buffer(current.bus_index, 0)) { - continue; //may have been removed - } - - AudioFrame *target = AudioServer::get_singleton()->thread_get_channel_mix_buffer(current.bus_index, 0); - - for (int j = 0; j < buffer_size; j++) { - target[j] += buffer[j] * vol; - vol += vol_inc; - } - - } else { - AudioFrame *targets[4]; - bool valid = true; - - for (int k = 0; k < cc; k++) { - if (!AudioServer::get_singleton()->thread_has_channel_mix_buffer(current.bus_index, k)) { - valid = false; //may have been removed - break; - } - - targets[k] = AudioServer::get_singleton()->thread_get_channel_mix_buffer(current.bus_index, k); - } - - if (!valid) { - continue; - } - - for (int j = 0; j < buffer_size; j++) { - AudioFrame frame = buffer[j] * vol; - for (int k = 0; k < cc; k++) { - targets[k][j] += frame; - } - vol += vol_inc; - } - } - - prev_outputs[i] = current; - } - - prev_output_count = oc; - - //stream is no longer active, disable this. - if (!stream_playback->is_playing()) { - active.clear(); - } - - output_ready.clear(); - stream_paused_fade_in = false; - stream_paused_fade_out = false; -} - void AudioStreamPlayer2D::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { - AudioServer::get_singleton()->add_callback(_mix_audios, this); + AudioServer::get_singleton()->add_listener_changed_callback(_listener_changed_cb, this); if (autoplay && !Engine::get_singleton()->is_editor_hint()) { play(); } } if (p_what == NOTIFICATION_EXIT_TREE) { - AudioServer::get_singleton()->remove_callback(_mix_audios, this); + stop(); + AudioServer::get_singleton()->remove_listener_changed_callback(_listener_changed_cb, this); } if (p_what == NOTIFICATION_PAUSED) { @@ -168,120 +60,143 @@ void AudioStreamPlayer2D::_notification(int p_what) { if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { //update anything related to position first, if possible of course + if (setplay.get() > 0 || (active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count())) { + _update_panning(); + } - if (!output_ready.is_set()) { - Ref<World2D> world_2d = get_world_2d(); - ERR_FAIL_COND(world_2d.is_null()); - - int new_output_count = 0; - - Vector2 global_pos = get_global_position(); - - int bus_index = AudioServer::get_singleton()->thread_find_bus_index(bus); - - //check if any area is diverting sound into a bus + if (setplay.get() >= 0 && stream.is_valid()) { + active.set(); + Ref<AudioStreamPlayback> new_playback = stream->instance_playback(); + ERR_FAIL_COND_MSG(new_playback.is_null(), "Failed to instantiate playback."); + AudioServer::get_singleton()->start_playback_stream(new_playback, _get_actual_bus(), volume_vector, setplay.get()); + stream_playbacks.push_back(new_playback); + setplay.set(-1); + } - PhysicsDirectSpaceState2D *space_state = PhysicsServer2D::get_singleton()->space_get_direct_state(world_2d->get_space()); + if (!stream_playbacks.is_empty() && active.is_set()) { + // Stop playing if no longer active. + Vector<Ref<AudioStreamPlayback>> playbacks_to_remove; + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (playback.is_valid() && !AudioServer::get_singleton()->is_playback_active(playback) && !AudioServer::get_singleton()->is_playback_paused(playback)) { + emit_signal(SNAME("finished")); + playbacks_to_remove.push_back(playback); + } + } + // Now go through and remove playbacks that have finished. Removing elements from a Vector in a range based for is asking for trouble. + for (Ref<AudioStreamPlayback> &playback : playbacks_to_remove) { + stream_playbacks.erase(playback); + } + if (!playbacks_to_remove.is_empty() && stream_playbacks.is_empty()) { + // This node is no longer actively playing audio. + active.clear(); + set_physics_process_internal(false); + } + } - PhysicsDirectSpaceState2D::ShapeResult sr[MAX_INTERSECT_AREAS]; + while (stream_playbacks.size() > max_polyphony) { + AudioServer::get_singleton()->stop_playback_stream(stream_playbacks[0]); + stream_playbacks.remove(0); + } + } +} - int areas = space_state->intersect_point(global_pos, sr, MAX_INTERSECT_AREAS, Set<RID>(), area_mask, false, true); +StringName AudioStreamPlayer2D::_get_actual_bus() { + Vector2 global_pos = get_global_position(); - for (int i = 0; i < areas; i++) { - Area2D *area2d = Object::cast_to<Area2D>(sr[i].collider); - if (!area2d) { - continue; - } + //check if any area is diverting sound into a bus + Ref<World2D> world_2d = get_world_2d(); + ERR_FAIL_COND_V(world_2d.is_null(), SNAME("Master")); - if (!area2d->is_overriding_audio_bus()) { - continue; - } + PhysicsDirectSpaceState2D *space_state = PhysicsServer2D::get_singleton()->space_get_direct_state(world_2d->get_space()); + PhysicsDirectSpaceState2D::ShapeResult sr[MAX_INTERSECT_AREAS]; - StringName bus_name = area2d->get_audio_bus_name(); - bus_index = AudioServer::get_singleton()->thread_find_bus_index(bus_name); - break; - } + int areas = space_state->intersect_point(global_pos, sr, MAX_INTERSECT_AREAS, Set<RID>(), area_mask, false, true); - const Set<Viewport *> viewports = world_2d->get_viewports(); + for (int i = 0; i < areas; i++) { + Area2D *area2d = Object::cast_to<Area2D>(sr[i].collider); + if (!area2d) { + continue; + } - for (Set<Viewport *>::Element *E = viewports.front(); E; E = E->next()) { - Viewport *vp = E->get(); - if (vp->is_audio_listener_2d()) { - //compute matrix to convert to screen - Transform2D to_screen = vp->get_global_canvas_transform() * vp->get_canvas_transform(); - Vector2 screen_size = vp->get_visible_rect().size; + if (!area2d->is_overriding_audio_bus()) { + continue; + } - //screen in global is used for attenuation - Vector2 screen_in_global = to_screen.affine_inverse().xform(screen_size * 0.5); + return area2d->get_audio_bus_name(); + } + return default_bus; +} - float dist = global_pos.distance_to(screen_in_global); //distance to screen center +void AudioStreamPlayer2D::_update_panning() { + if (!active.is_set() || stream.is_null()) { + return; + } - if (dist > max_distance) { - continue; //can't hear this sound in this viewport - } + Ref<World2D> world_2d = get_world_2d(); + ERR_FAIL_COND(world_2d.is_null()); - float multiplier = Math::pow(1.0f - dist / max_distance, attenuation); - multiplier *= Math::db2linear(volume_db); //also apply player volume! + Vector2 global_pos = get_global_position(); - //point in screen is used for panning - Vector2 point_in_screen = to_screen.xform(global_pos); + Set<Viewport *> viewports = world_2d->get_viewports(); + viewports.insert(get_viewport()); // TODO: This is a mediocre workaround for #50958. Remove when that bug is fixed! - float pan = CLAMP(point_in_screen.x / screen_size.width, 0.0, 1.0); + volume_vector.resize(4); + volume_vector.write[0] = AudioFrame(0, 0); + volume_vector.write[1] = AudioFrame(0, 0); + volume_vector.write[2] = AudioFrame(0, 0); + volume_vector.write[3] = AudioFrame(0, 0); - float l = 1.0 - pan; - float r = pan; + for (Viewport *vp : viewports) { + if (!vp->is_audio_listener_2d()) { + continue; + } + //compute matrix to convert to screen + Vector2 screen_size = vp->get_visible_rect().size; + Vector2 listener_in_global; + Vector2 relative_to_listener; + + //screen in global is used for attenuation + Listener2D *listener = vp->get_listener_2d(); + if (listener) { + listener_in_global = listener->get_global_position(); + relative_to_listener = global_pos - listener_in_global; + } else { + Transform2D to_listener = vp->get_global_canvas_transform() * vp->get_canvas_transform(); + listener_in_global = to_listener.affine_inverse().xform(screen_size * 0.5); + relative_to_listener = to_listener.xform(global_pos) - screen_size * 0.5; + } - outputs[new_output_count].vol = AudioFrame(l, r) * multiplier; - outputs[new_output_count].bus_index = bus_index; - outputs[new_output_count].viewport = vp; //keep pointer only for reference - new_output_count++; - if (new_output_count == MAX_OUTPUTS) { - break; - } - } - } + float dist = global_pos.distance_to(listener_in_global); // Distance to listener, or screen if none. - output_count.set(new_output_count); - output_ready.set(); + if (dist > max_distance) { + continue; //can't hear this sound in this viewport } - //start playing if requested - if (setplay.get() >= 0.0) { - setseek.set(setplay.get()); - active.set(); - setplay.set(-1); - } + float multiplier = Math::pow(1.0f - dist / max_distance, attenuation); + multiplier *= Math::db2linear(volume_db); //also apply player volume! - //stop playing if no longer active - if (!active.is_set()) { - set_physics_process_internal(false); - emit_signal("finished"); - } - } -} + float pan = CLAMP((relative_to_listener.x + screen_size.x * 0.5) / screen_size.x, 0.0, 1.0); -void AudioStreamPlayer2D::set_stream(Ref<AudioStream> p_stream) { - AudioServer::get_singleton()->lock(); + float l = 1.0 - pan; + float r = pan; - mix_buffer.resize(AudioServer::get_singleton()->thread_get_mix_buffer_size()); + volume_vector.write[0] = AudioFrame(l, r) * multiplier; + } - if (stream_playback.is_valid()) { - stream_playback.unref(); - stream.unref(); - active.clear(); - setseek.set(-1); + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_bus_exclusive(playback, _get_actual_bus(), volume_vector); } - if (p_stream.is_valid()) { - stream = p_stream; - stream_playback = p_stream->instance_playback(); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_pitch_scale(playback, pitch_scale); } - AudioServer::get_singleton()->unlock(); + last_mix_count = AudioServer::get_singleton()->get_mix_count(); +} - if (p_stream.is_valid() && stream_playback.is_null()) { - stream.unref(); - } +void AudioStreamPlayer2D::set_stream(Ref<AudioStream> p_stream) { + stop(); + stream = p_stream; } Ref<AudioStream> AudioStreamPlayer2D::get_stream() const { @@ -299,6 +214,9 @@ float AudioStreamPlayer2D::get_volume_db() const { void AudioStreamPlayer2D::set_pitch_scale(float p_pitch_scale) { ERR_FAIL_COND(p_pitch_scale <= 0.0); pitch_scale = p_pitch_scale; + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_pitch_scale(playback, p_pitch_scale); + } } float AudioStreamPlayer2D::get_pitch_scale() const { @@ -306,66 +224,64 @@ float AudioStreamPlayer2D::get_pitch_scale() const { } void AudioStreamPlayer2D::play(float p_from_pos) { - if (!is_playing()) { - // Reset the prev_output_count if the stream is stopped - prev_output_count = 0; + if (stream.is_null()) { + return; } - - if (stream_playback.is_valid()) { - setplay.set(p_from_pos); - output_ready.clear(); - set_physics_process_internal(true); + ERR_FAIL_COND_MSG(!is_inside_tree(), "Playback can only happen when a node is inside the scene tree"); + if (stream->is_monophonic() && is_playing()) { + stop(); } + + setplay.set(p_from_pos); + active.set(); + set_physics_process_internal(true); } void AudioStreamPlayer2D::seek(float p_seconds) { - if (stream_playback.is_valid()) { - setseek.set(p_seconds); + if (is_playing()) { + stop(); + play(p_seconds); } } void AudioStreamPlayer2D::stop() { - if (stream_playback.is_valid()) { - active.clear(); - set_physics_process_internal(false); - setplay.set(-1); + setplay.set(-1); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->stop_playback_stream(playback); } + stream_playbacks.clear(); + active.clear(); + set_physics_process_internal(false); } bool AudioStreamPlayer2D::is_playing() const { - if (stream_playback.is_valid()) { - return active.is_set() || setplay.get() >= 0; + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (AudioServer::get_singleton()->is_playback_active(playback)) { + return true; + } } - return false; } float AudioStreamPlayer2D::get_playback_position() { - if (stream_playback.is_valid()) { - float ss = setseek.get(); - if (ss >= 0.0) { - return ss; - } - return stream_playback->get_playback_position(); + // Return the playback position of the most recently started playback stream. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->get_playback_position(stream_playbacks[stream_playbacks.size() - 1]); } - return 0; } void AudioStreamPlayer2D::set_bus(const StringName &p_bus) { - //if audio is active, must lock this - AudioServer::get_singleton()->lock(); - bus = p_bus; - AudioServer::get_singleton()->unlock(); + default_bus = p_bus; // This will be pushed to the audio server during the next physics timestep, which is fast enough. } StringName AudioStreamPlayer2D::get_bus() const { for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) { - if (AudioServer::get_singleton()->get_bus_name(i) == bus) { - return bus; + if (AudioServer::get_singleton()->get_bus_name(i) == default_bus) { + return default_bus; } } - return "Master"; + return SNAME("Master"); } void AudioStreamPlayer2D::set_autoplay(bool p_enable) { @@ -433,19 +349,35 @@ uint32_t AudioStreamPlayer2D::get_area_mask() const { } void AudioStreamPlayer2D::set_stream_paused(bool p_pause) { - if (p_pause != stream_paused) { - stream_paused = p_pause; - stream_paused_fade_in = !p_pause; - stream_paused_fade_out = p_pause; + // TODO this does not have perfect recall, fix that maybe? If there are zero playbacks registered with the AudioServer, this bool isn't persisted. + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_paused(playback, p_pause); } } bool AudioStreamPlayer2D::get_stream_paused() const { - return stream_paused; + // There's currently no way to pause some playback streams but not others. Check the first and don't bother looking at the rest. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->is_playback_paused(stream_playbacks[0]); + } + return false; } Ref<AudioStreamPlayback> AudioStreamPlayer2D::get_stream_playback() { - return stream_playback; + if (!stream_playbacks.is_empty()) { + return stream_playbacks[stream_playbacks.size() - 1]; + } + return nullptr; +} + +void AudioStreamPlayer2D::set_max_polyphony(int p_max_polyphony) { + if (p_max_polyphony > 0) { + max_polyphony = p_max_polyphony; + } +} + +int AudioStreamPlayer2D::get_max_polyphony() const { + return max_polyphony; } void AudioStreamPlayer2D::_bind_methods() { @@ -486,6 +418,9 @@ void AudioStreamPlayer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer2D::set_stream_paused); ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer2D::get_stream_paused); + 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("get_stream_playback"), &AudioStreamPlayer2D::get_stream_playback); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream"); @@ -496,6 +431,7 @@ void AudioStreamPlayer2D::_bind_methods() { 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, "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::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"); diff --git a/scene/2d/audio_stream_player_2d.h b/scene/2d/audio_stream_player_2d.h index 21f524c703..5360fd4934 100644 --- a/scene/2d/audio_stream_player_2d.h +++ b/scene/2d/audio_stream_player_2d.h @@ -31,7 +31,6 @@ #ifndef AUDIO_STREAM_PLAYER_2D_H #define AUDIO_STREAM_PLAYER_2D_H -#include "core/templates/safe_refcount.h" #include "scene/2d/node_2d.h" #include "servers/audio/audio_stream.h" #include "servers/audio_server.h" @@ -52,38 +51,31 @@ private: Viewport *viewport = nullptr; //pointer only used for reference to previous mix }; - Output outputs[MAX_OUTPUTS]; - SafeNumeric<int> output_count; - SafeFlag output_ready; - - //these are used by audio thread to have a reference of previous volumes (for ramping volume and avoiding clicks) - Output prev_outputs[MAX_OUTPUTS]; - int prev_output_count = 0; - - Ref<AudioStreamPlayback> stream_playback; + Vector<Ref<AudioStreamPlayback>> stream_playbacks; Ref<AudioStream> stream; - Vector<AudioFrame> mix_buffer; - SafeNumeric<float> setseek{ -1.0 }; - SafeFlag active; + SafeFlag active{ false }; SafeNumeric<float> setplay{ -1.0 }; + Vector<AudioFrame> volume_vector; + + uint64_t last_mix_count = -1; + float volume_db = 0.0; float pitch_scale = 1.0; bool autoplay = false; - bool stream_paused = false; - bool stream_paused_fade_in = false; - bool stream_paused_fade_out = false; - StringName bus; - - void _mix_audio(); - static void _mix_audios(void *self) { reinterpret_cast<AudioStreamPlayer2D *>(self)->_mix_audio(); } + StringName default_bus = SNAME("Master"); + int max_polyphony = 1; void _set_playing(bool p_enable); bool _is_active() const; + StringName _get_actual_bus(); + void _update_panning(); void _bus_layout_changed(); + static void _listener_changed_cb(void *self) { reinterpret_cast<AudioStreamPlayer2D *>(self)->_update_panning(); } + uint32_t area_mask = 1; float max_distance = 2000.0; @@ -128,6 +120,9 @@ public: void set_stream_paused(bool p_pause); bool get_stream_paused() const; + void set_max_polyphony(int p_max_polyphony); + int get_max_polyphony() const; + Ref<AudioStreamPlayback> get_stream_playback(); AudioStreamPlayer2D(); diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp index f293081987..bf91ce8e65 100644 --- a/scene/2d/camera_2d.cpp +++ b/scene/2d/camera_2d.cpp @@ -30,10 +30,7 @@ #include "camera_2d.h" -#include "core/config/engine.h" -#include "core/math/math_funcs.h" -#include "scene/scene_string_names.h" -#include "servers/rendering_server.h" +#include "scene/main/window.h" void Camera2D::_update_scroll() { if (!is_inside_tree()) { @@ -99,7 +96,7 @@ Transform2D Camera2D::get_camera_transform() { Size2 screen_size = _get_camera_screen_size(); - Point2 new_camera_pos = get_global_transform().get_origin(); + Point2 new_camera_pos = get_global_position(); Point2 ret_camera_pos; if (!first) { @@ -172,7 +169,7 @@ Transform2D Camera2D::get_camera_transform() { Point2 screen_offset = (anchor_mode == ANCHOR_MODE_DRAG_CENTER ? (screen_size * 0.5 * zoom) : Point2()); - real_t angle = get_global_transform().get_rotation(); + real_t angle = get_global_rotation(); if (rotating) { screen_offset = screen_offset.rotated(angle); } @@ -241,6 +238,10 @@ void Camera2D::_notification(int p_what) { viewport = get_viewport(); } + if (is_current()) { + viewport->_camera_2d_set(this); + } + canvas = get_canvas(); RID vp = viewport->get_viewport_rid(); @@ -259,6 +260,8 @@ void Camera2D::_notification(int p_what) { if (is_current()) { if (viewport && !(custom_viewport && !ObjectDB::get_instance(custom_viewport_id))) { viewport->set_canvas_transform(Transform2D()); + clear_current(); + current = true; } } remove_from_group(group_name); @@ -303,8 +306,8 @@ void Camera2D::_notification(int p_what) { limit_drawing_width = 3; } - Vector2 camera_origin = get_global_transform().get_origin(); - Vector2 camera_scale = get_global_transform().get_scale().abs(); + Vector2 camera_origin = get_global_position(); + Vector2 camera_scale = get_global_scale().abs(); Vector2 limit_points[4] = { (Vector2(limit[SIDE_LEFT], limit[SIDE_TOP]) - camera_origin) / camera_scale, (Vector2(limit[SIDE_RIGHT], limit[SIDE_TOP]) - camera_origin) / camera_scale, @@ -392,18 +395,29 @@ Camera2D::Camera2DProcessCallback Camera2D::get_process_callback() const { void Camera2D::_make_current(Object *p_which) { if (p_which == this) { current = true; + if (is_inside_tree()) { + get_viewport()->_camera_2d_set(this); + update(); + } } else { current = false; + if (is_inside_tree()) { + if (get_viewport()->get_camera_2d() == this) { + get_viewport()->_camera_2d_set(nullptr); + } + update(); + } } } -void Camera2D::_set_current(bool p_current) { +void Camera2D::set_current(bool p_current) { if (p_current) { make_current(); + } else { + if (current) { + clear_current(); + } } - - current = p_current; - update(); } bool Camera2D::is_current() const { @@ -411,18 +425,19 @@ bool Camera2D::is_current() const { } void Camera2D::make_current() { - if (!is_inside_tree()) { - current = true; - } else { + if (is_inside_tree()) { get_tree()->call_group_flags(SceneTree::GROUP_CALL_REALTIME, group_name, "_make_current", this); + } else { + current = true; } _update_scroll(); } void Camera2D::clear_current() { - current = false; if (is_inside_tree()) { get_tree()->call_group_flags(SceneTree::GROUP_CALL_REALTIME, group_name, "_make_current", (Object *)nullptr); + } else { + current = false; } } @@ -466,8 +481,8 @@ void Camera2D::force_update_scroll() { } void Camera2D::reset_smoothing() { - smoothed_camera_pos = camera_pos; _update_scroll(); + smoothed_camera_pos = camera_pos; } void Camera2D::align() { @@ -475,7 +490,7 @@ void Camera2D::align() { Size2 screen_size = _get_camera_screen_size(); - Point2 current_camera_pos = get_global_transform().get_origin(); + Point2 current_camera_pos = get_global_position(); if (anchor_mode == ANCHOR_MODE_DRAG_CENTER) { if (drag_horizontal_offset < 0) { camera_pos.x = current_camera_pos.x + screen_size.x * 0.5 * drag_margin[SIDE_RIGHT] * drag_horizontal_offset; @@ -659,17 +674,14 @@ void Camera2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_rotating", "rotating"), &Camera2D::set_rotating); ClassDB::bind_method(D_METHOD("is_rotating"), &Camera2D::is_rotating); - ClassDB::bind_method(D_METHOD("make_current"), &Camera2D::make_current); - ClassDB::bind_method(D_METHOD("clear_current"), &Camera2D::clear_current); - ClassDB::bind_method(D_METHOD("_make_current"), &Camera2D::_make_current); - ClassDB::bind_method(D_METHOD("_update_scroll"), &Camera2D::_update_scroll); ClassDB::bind_method(D_METHOD("set_process_callback", "mode"), &Camera2D::set_process_callback); ClassDB::bind_method(D_METHOD("get_process_callback"), &Camera2D::get_process_callback); - ClassDB::bind_method(D_METHOD("_set_current", "current"), &Camera2D::_set_current); + ClassDB::bind_method(D_METHOD("set_current", "current"), &Camera2D::set_current); ClassDB::bind_method(D_METHOD("is_current"), &Camera2D::is_current); + ClassDB::bind_method(D_METHOD("_make_current"), &Camera2D::_make_current); ClassDB::bind_method(D_METHOD("set_limit", "margin", "limit"), &Camera2D::set_limit); ClassDB::bind_method(D_METHOD("get_limit", "margin"), &Camera2D::get_limit); @@ -725,7 +737,7 @@ void Camera2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "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::BOOL, "current"), "set_current", "is_current"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "zoom"), "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"); diff --git a/scene/2d/camera_2d.h b/scene/2d/camera_2d.h index 7494e526cc..d697515547 100644 --- a/scene/2d/camera_2d.h +++ b/scene/2d/camera_2d.h @@ -32,7 +32,6 @@ #define CAMERA_2D_H #include "scene/2d/node_2d.h" -#include "scene/main/window.h" class Camera2D : public Node2D { GDCLASS(Camera2D, Node2D); @@ -83,7 +82,7 @@ protected: void _update_scroll(); void _make_current(Object *p_which); - void _set_current(bool p_current); + void set_current(bool p_current); void _set_old_smoothing(real_t p_enable); diff --git a/scene/2d/collision_object_2d.cpp b/scene/2d/collision_object_2d.cpp index 93d154bb01..5d3a538f60 100644 --- a/scene/2d/collision_object_2d.cpp +++ b/scene/2d/collision_object_2d.cpp @@ -147,36 +147,40 @@ uint32_t CollisionObject2D::get_collision_mask() const { return collision_mask; } -void CollisionObject2D::set_collision_layer_bit(int p_bit, bool p_value) { - ERR_FAIL_INDEX_MSG(p_bit, 32, "Collision layer bit must be between 0 and 31 inclusive."); +void CollisionObject2D::set_collision_layer_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive."); uint32_t collision_layer = get_collision_layer(); if (p_value) { - collision_layer |= 1 << p_bit; + collision_layer |= 1 << (p_layer_number - 1); } else { - collision_layer &= ~(1 << p_bit); + collision_layer &= ~(1 << (p_layer_number - 1)); } set_collision_layer(collision_layer); } -bool CollisionObject2D::get_collision_layer_bit(int p_bit) const { - ERR_FAIL_INDEX_V_MSG(p_bit, 32, false, "Collision layer bit must be between 0 and 31 inclusive."); - return get_collision_layer() & (1 << p_bit); +bool CollisionObject2D::get_collision_layer_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Collision layer number must be between 1 and 32 inclusive."); + return get_collision_layer() & (1 << (p_layer_number - 1)); } -void CollisionObject2D::set_collision_mask_bit(int p_bit, bool p_value) { - ERR_FAIL_INDEX_MSG(p_bit, 32, "Collision mask bit must be between 0 and 31 inclusive."); +void CollisionObject2D::set_collision_mask_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive."); uint32_t mask = get_collision_mask(); if (p_value) { - mask |= 1 << p_bit; + mask |= 1 << (p_layer_number - 1); } else { - mask &= ~(1 << p_bit); + mask &= ~(1 << (p_layer_number - 1)); } set_collision_mask(mask); } -bool CollisionObject2D::get_collision_mask_bit(int p_bit) const { - ERR_FAIL_INDEX_V_MSG(p_bit, 32, false, "Collision mask bit must be between 0 and 31 inclusive."); - return get_collision_mask() & (1 << p_bit); +bool CollisionObject2D::get_collision_mask_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Collision layer number must be between 1 and 32 inclusive."); + return get_collision_mask() & (1 << (p_layer_number - 1)); } void CollisionObject2D::set_disable_mode(DisableMode p_mode) { @@ -477,10 +481,8 @@ bool CollisionObject2D::is_pickable() const { return pickable; } -void CollisionObject2D::_input_event(Node *p_viewport, const Ref<InputEvent> &p_input_event, int p_shape) { - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_input_event, p_viewport, p_input_event, p_shape); - } +void CollisionObject2D::_input_event_call(Viewport *p_viewport, const Ref<InputEvent> &p_input_event, int p_shape) { + GDVIRTUAL_CALL(_input_event, p_viewport, p_input_event, p_shape); emit_signal(SceneStringNames::get_singleton()->input_event, p_viewport, p_input_event, p_shape); } @@ -498,6 +500,20 @@ void CollisionObject2D::_mouse_exit() { emit_signal(SceneStringNames::get_singleton()->mouse_exited); } +void CollisionObject2D::_mouse_shape_enter(int p_shape) { + if (get_script_instance()) { + get_script_instance()->call(SceneStringNames::get_singleton()->_mouse_shape_enter, p_shape); + } + emit_signal(SceneStringNames::get_singleton()->mouse_shape_entered, p_shape); +} + +void CollisionObject2D::_mouse_shape_exit(int p_shape) { + if (get_script_instance()) { + get_script_instance()->call(SceneStringNames::get_singleton()->_mouse_shape_exit, p_shape); + } + emit_signal(SceneStringNames::get_singleton()->mouse_shape_exited, p_shape); +} + void CollisionObject2D::set_only_update_transform_changes(bool p_enable) { only_update_transform_changes = p_enable; } @@ -551,10 +567,10 @@ void CollisionObject2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_collision_layer"), &CollisionObject2D::get_collision_layer); ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &CollisionObject2D::set_collision_mask); ClassDB::bind_method(D_METHOD("get_collision_mask"), &CollisionObject2D::get_collision_mask); - ClassDB::bind_method(D_METHOD("set_collision_layer_bit", "bit", "value"), &CollisionObject2D::set_collision_layer_bit); - ClassDB::bind_method(D_METHOD("get_collision_layer_bit", "bit"), &CollisionObject2D::get_collision_layer_bit); - ClassDB::bind_method(D_METHOD("set_collision_mask_bit", "bit", "value"), &CollisionObject2D::set_collision_mask_bit); - ClassDB::bind_method(D_METHOD("get_collision_mask_bit", "bit"), &CollisionObject2D::get_collision_mask_bit); + ClassDB::bind_method(D_METHOD("set_collision_layer_value", "layer_number", "value"), &CollisionObject2D::set_collision_layer_value); + ClassDB::bind_method(D_METHOD("get_collision_layer_value", "layer_number"), &CollisionObject2D::get_collision_layer_value); + ClassDB::bind_method(D_METHOD("set_collision_mask_value", "layer_number", "value"), &CollisionObject2D::set_collision_mask_value); + ClassDB::bind_method(D_METHOD("get_collision_mask_value", "layer_number"), &CollisionObject2D::get_collision_mask_value); ClassDB::bind_method(D_METHOD("set_disable_mode", "mode"), &CollisionObject2D::set_disable_mode); ClassDB::bind_method(D_METHOD("get_disable_mode"), &CollisionObject2D::get_disable_mode); ClassDB::bind_method(D_METHOD("set_pickable", "enabled"), &CollisionObject2D::set_pickable); @@ -579,11 +595,13 @@ void CollisionObject2D::_bind_methods() { ClassDB::bind_method(D_METHOD("shape_owner_clear_shapes", "owner_id"), &CollisionObject2D::shape_owner_clear_shapes); ClassDB::bind_method(D_METHOD("shape_find_owner", "shape_index"), &CollisionObject2D::shape_find_owner); - BIND_VMETHOD(MethodInfo("_input_event", PropertyInfo(Variant::OBJECT, "viewport"), PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"), PropertyInfo(Variant::INT, "shape_idx"))); + GDVIRTUAL_BIND(_input_event, "viewport", "event", "shape_idx"); ADD_SIGNAL(MethodInfo("input_event", PropertyInfo(Variant::OBJECT, "viewport", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"), PropertyInfo(Variant::INT, "shape_idx"))); ADD_SIGNAL(MethodInfo("mouse_entered")); ADD_SIGNAL(MethodInfo("mouse_exited")); + 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"); diff --git a/scene/2d/collision_object_2d.h b/scene/2d/collision_object_2d.h index 7a71affbb5..19abacb201 100644 --- a/scene/2d/collision_object_2d.h +++ b/scene/2d/collision_object_2d.h @@ -32,6 +32,7 @@ #define COLLISION_OBJECT_2D_H #include "scene/2d/node_2d.h" +#include "scene/main/viewport.h" #include "scene/resources/shape_2d.h" #include "servers/physics_server_2d.h" @@ -75,7 +76,7 @@ private: int total_subshapes = 0; Map<uint32_t, ShapeData> shapes; - bool only_update_transform_changes = false; //this is used for sync physics in CharacterBody2D + bool only_update_transform_changes = false; // This is used for sync to physics. void _apply_disabled(); void _apply_enabled(); @@ -88,15 +89,19 @@ protected: void _update_pickable(); friend class Viewport; - void _input_event(Node *p_viewport, const Ref<InputEvent> &p_input_event, int p_shape); + void _input_event_call(Viewport *p_viewport, const Ref<InputEvent> &p_input_event, int p_shape); void _mouse_enter(); void _mouse_exit(); + void _mouse_shape_enter(int p_shape); + void _mouse_shape_exit(int p_shape); + void set_only_update_transform_changes(bool p_enable); bool is_only_update_transform_changes_enabled() const; void set_body_mode(PhysicsServer2D::BodyMode p_mode); + GDVIRTUAL3(_input_event, Viewport *, Ref<InputEvent>, int) public: void set_collision_layer(uint32_t p_layer); uint32_t get_collision_layer() const; @@ -104,11 +109,11 @@ public: void set_collision_mask(uint32_t p_mask); uint32_t get_collision_mask() const; - void set_collision_layer_bit(int p_bit, bool p_value); - bool get_collision_layer_bit(int p_bit) const; + void set_collision_layer_value(int p_layer_number, bool p_value); + bool get_collision_layer_value(int p_layer_number) const; - void set_collision_mask_bit(int p_bit, bool p_value); - bool get_collision_mask_bit(int p_bit) const; + void set_collision_mask_value(int p_layer_number, bool p_value); + bool get_collision_mask_value(int p_layer_number) const; void set_disable_mode(DisableMode p_mode); DisableMode get_disable_mode() const; diff --git a/scene/2d/collision_polygon_2d.cpp b/scene/2d/collision_polygon_2d.cpp index 2a2fde80e2..8c8a292ad7 100644 --- a/scene/2d/collision_polygon_2d.cpp +++ b/scene/2d/collision_polygon_2d.cpp @@ -31,7 +31,6 @@ #include "collision_polygon_2d.h" #include "collision_object_2d.h" -#include "core/config/engine.h" #include "core/math/geometry_2d.h" #include "scene/resources/concave_polygon_shape_2d.h" #include "scene/resources/convex_polygon_shape_2d.h" diff --git a/scene/2d/collision_polygon_2d.h b/scene/2d/collision_polygon_2d.h index 95dd8c9e21..6b32923010 100644 --- a/scene/2d/collision_polygon_2d.h +++ b/scene/2d/collision_polygon_2d.h @@ -32,7 +32,6 @@ #define COLLISION_POLYGON_2D_H #include "scene/2d/node_2d.h" -#include "scene/resources/shape_2d.h" class CollisionObject2D; diff --git a/scene/2d/collision_shape_2d.cpp b/scene/2d/collision_shape_2d.cpp index 60780f1cc3..d52795f0d5 100644 --- a/scene/2d/collision_shape_2d.cpp +++ b/scene/2d/collision_shape_2d.cpp @@ -31,14 +31,8 @@ #include "collision_shape_2d.h" #include "collision_object_2d.h" -#include "core/config/engine.h" -#include "scene/resources/capsule_shape_2d.h" -#include "scene/resources/circle_shape_2d.h" #include "scene/resources/concave_polygon_shape_2d.h" #include "scene/resources/convex_polygon_shape_2d.h" -#include "scene/resources/line_shape_2d.h" -#include "scene/resources/rectangle_shape_2d.h" -#include "scene/resources/segment_shape_2d.h" void CollisionShape2D::_shape_changed() { update(); diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp index 24f3301ce1..b836497627 100644 --- a/scene/2d/cpu_particles_2d.cpp +++ b/scene/2d/cpu_particles_2d.cpp @@ -32,9 +32,7 @@ #include "core/core_string_names.h" #include "scene/2d/gpu_particles_2d.h" -#include "scene/main/canvas_item.h" #include "scene/resources/particles_material.h" -#include "servers/rendering_server.h" void CPUParticles2D::set_emitting(bool p_emitting) { if (emitting == p_emitting) { @@ -65,7 +63,7 @@ void CPUParticles2D::set_amount(int p_amount) { particle_order.resize(p_amount); } -void CPUParticles2D::set_lifetime(float p_lifetime) { +void CPUParticles2D::set_lifetime(double p_lifetime) { ERR_FAIL_COND_MSG(p_lifetime <= 0, "Particles lifetime must be greater than 0."); lifetime = p_lifetime; } @@ -74,7 +72,7 @@ void CPUParticles2D::set_one_shot(bool p_one_shot) { one_shot = p_one_shot; } -void CPUParticles2D::set_pre_process_time(float p_time) { +void CPUParticles2D::set_pre_process_time(double p_time) { pre_process_time = p_time; } @@ -86,7 +84,7 @@ void CPUParticles2D::set_randomness_ratio(real_t p_ratio) { randomness_ratio = p_ratio; } -void CPUParticles2D::set_lifetime_randomness(float p_random) { +void CPUParticles2D::set_lifetime_randomness(double p_random) { lifetime_randomness = p_random; } @@ -95,7 +93,7 @@ void CPUParticles2D::set_use_local_coordinates(bool p_enable) { set_notify_transform(!p_enable); } -void CPUParticles2D::set_speed_scale(real_t p_scale) { +void CPUParticles2D::set_speed_scale(double p_scale) { speed_scale = p_scale; } @@ -107,7 +105,7 @@ int CPUParticles2D::get_amount() const { return particles.size(); } -float CPUParticles2D::get_lifetime() const { +double CPUParticles2D::get_lifetime() const { return lifetime; } @@ -115,7 +113,7 @@ bool CPUParticles2D::get_one_shot() const { return one_shot; } -float CPUParticles2D::get_pre_process_time() const { +double CPUParticles2D::get_pre_process_time() const { return pre_process_time; } @@ -127,7 +125,7 @@ real_t CPUParticles2D::get_randomness_ratio() const { return randomness_ratio; } -float CPUParticles2D::get_lifetime_randomness() const { +double CPUParticles2D::get_lifetime_randomness() const { return lifetime_randomness; } @@ -135,7 +133,7 @@ bool CPUParticles2D::get_use_local_coordinates() const { return local_coords; } -real_t CPUParticles2D::get_speed_scale() const { +double CPUParticles2D::get_speed_scale() const { return speed_scale; } @@ -250,7 +248,7 @@ TypedArray<String> CPUParticles2D::get_configuration_warnings() const { CanvasItemMaterial *mat = Object::cast_to<CanvasItemMaterial>(get_material().ptr()); if (get_material().is_null() || (mat && !mat->get_particles_animation())) { - if (get_param(PARAM_ANIM_SPEED) != 0.0 || get_param(PARAM_ANIM_OFFSET) != 0.0 || + if (get_param_max(PARAM_ANIM_SPEED) != 0.0 || get_param_max(PARAM_ANIM_OFFSET) != 0.0 || get_param_curve(PARAM_ANIM_SPEED).is_valid() || get_param_curve(PARAM_ANIM_OFFSET).is_valid()) { warnings.push_back(TTR("CPUParticles2D animation requires the usage of a CanvasItemMaterial with \"Particles Animation\" enabled.")); } @@ -294,28 +292,34 @@ real_t CPUParticles2D::get_spread() const { return spread; } -void CPUParticles2D::set_param(Parameter p_param, real_t p_value) { +void CPUParticles2D::set_param_min(Parameter p_param, real_t p_value) { ERR_FAIL_INDEX(p_param, PARAM_MAX); - parameters[p_param] = p_value; + parameters_min[p_param] = p_value; + if (parameters_min[p_param] > parameters_max[p_param]) { + set_param_max(p_param, p_value); + } } -real_t CPUParticles2D::get_param(Parameter p_param) const { +real_t CPUParticles2D::get_param_min(Parameter p_param) const { ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0); - return parameters[p_param]; + return parameters_min[p_param]; } -void CPUParticles2D::set_param_randomness(Parameter p_param, real_t p_value) { +void CPUParticles2D::set_param_max(Parameter p_param, real_t p_value) { ERR_FAIL_INDEX(p_param, PARAM_MAX); - randomness[p_param] = p_value; + parameters_max[p_param] = p_value; + if (parameters_min[p_param] > parameters_max[p_param]) { + set_param_min(p_param, p_value); + } } -real_t CPUParticles2D::get_param_randomness(Parameter p_param) const { +real_t CPUParticles2D::get_param_max(Parameter p_param) const { ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0); - return randomness[p_param]; + return parameters_max[p_param]; } static void _adjust_curve_range(const Ref<Curve> &p_curve, real_t p_min, real_t p_max) { @@ -462,11 +466,32 @@ Vector2 CPUParticles2D::get_gravity() const { return gravity; } -void CPUParticles2D::_validate_property(PropertyInfo &property) const { - if (property.name == "color" && color_ramp.is_valid()) { - property.usage = PROPERTY_USAGE_NONE; - } +void CPUParticles2D::set_scale_curve_x(Ref<Curve> p_scale_curve) { + scale_curve_x = p_scale_curve; +} + +void CPUParticles2D::set_scale_curve_y(Ref<Curve> p_scale_curve) { + scale_curve_y = p_scale_curve; +} + +void CPUParticles2D::set_split_scale(bool p_split_scale) { + split_scale = p_split_scale; + notify_property_list_changed(); +} + +Ref<Curve> CPUParticles2D::get_scale_curve_x() const { + return scale_curve_x; +} + +Ref<Curve> CPUParticles2D::get_scale_curve_y() const { + return scale_curve_y; +} + +bool CPUParticles2D::get_split_scale() { + return split_scale; +} +void CPUParticles2D::_validate_property(PropertyInfo &property) const { if (property.name == "emission_sphere_radius" && emission_shape != EMISSION_SHAPE_SPHERE) { property.usage = PROPERTY_USAGE_NONE; } @@ -490,6 +515,9 @@ void CPUParticles2D::_validate_property(PropertyInfo &property) const { if (property.name == "emission_colors" && emission_shape != EMISSION_SHAPE_POINTS && emission_shape != EMISSION_SHAPE_DIRECTED_POINTS) { property.usage = PROPERTY_USAGE_NONE; } + if (property.name.begins_with("scale_curve_") && !split_scale) { + property.usage = PROPERTY_USAGE_NONE; + } } static uint32_t idhash(uint32_t x) { @@ -520,7 +548,7 @@ void CPUParticles2D::_update_internal() { return; } - float delta = get_process_delta_time(); + double delta = get_process_delta_time(); if (emitting) { inactive_time = 0; } else { @@ -540,14 +568,14 @@ void CPUParticles2D::_update_internal() { _set_redraw(true); if (time == 0 && pre_process_time > 0.0) { - float frame_time; + double frame_time; if (fixed_fps > 0) { frame_time = 1.0 / fixed_fps; } else { frame_time = 1.0 / 30.0; } - float todo = pre_process_time; + double todo = pre_process_time; while (todo >= 0) { _particles_process(frame_time); @@ -556,16 +584,16 @@ void CPUParticles2D::_update_internal() { } if (fixed_fps > 0) { - float frame_time = 1.0 / fixed_fps; - float decr = frame_time; + double frame_time = 1.0 / fixed_fps; + double decr = frame_time; - float ldelta = delta; + double ldelta = delta; if (ldelta > 0.1) { //avoid recursive stalls if fps goes below 10 ldelta = 0.1; } else if (ldelta <= 0.0) { //unlikely but.. ldelta = 0.001; } - float todo = frame_remainder + ldelta; + double todo = frame_remainder + ldelta; while (todo >= frame_time) { _particles_process(frame_time); @@ -581,7 +609,7 @@ void CPUParticles2D::_update_internal() { _update_particle_data_buffer(); } -void CPUParticles2D::_particles_process(float p_delta) { +void CPUParticles2D::_particles_process(double p_delta) { p_delta *= speed_scale; int pcount = particles.size(); @@ -589,7 +617,7 @@ void CPUParticles2D::_particles_process(float p_delta) { Particle *parray = w; - float prev_time = time; + double prev_time = time; time += p_delta; if (time > lifetime) { time = Math::fmod(time, lifetime); @@ -608,7 +636,7 @@ void CPUParticles2D::_particles_process(float p_delta) { velocity_xform[2] = Vector2(); } - float system_phase = time / lifetime; + double system_phase = time / lifetime; for (int i = 0; i < pcount; i++) { Particle &p = parray[i]; @@ -617,12 +645,12 @@ void CPUParticles2D::_particles_process(float p_delta) { continue; } - float local_delta = p_delta; + double local_delta = p_delta; // The phase is a ratio between 0 (birth) and 1 (end of life) for each particle. // While we use time in tests later on, for randomness we use the phase as done in the // original shader code, and we later multiply by lifetime to get the time. - real_t restart_phase = real_t(i) / real_t(pcount); + double restart_phase = double(i) / double(pcount); if (randomness_ratio > 0.0) { uint32_t seed = cycle; @@ -631,12 +659,12 @@ void CPUParticles2D::_particles_process(float p_delta) { } seed *= uint32_t(pcount); seed += uint32_t(i); - real_t random = (idhash(seed) % uint32_t(65536)) / 65536.0; - restart_phase += randomness_ratio * random * 1.0 / pcount; + double random = double(idhash(seed) % uint32_t(65536)) / 65536.0; + restart_phase += randomness_ratio * random * 1.0 / double(pcount); } restart_phase *= (1.0 - explosiveness_ratio); - float restart_time = restart_phase * lifetime; + double restart_time = restart_phase * lifetime; bool restart = false; if (time > prev_time) { @@ -701,14 +729,14 @@ void CPUParticles2D::_particles_process(float p_delta) { real_t angle1_rad = Math::atan2(direction.y, direction.x) + Math::deg2rad((Math::randf() * 2.0 - 1.0) * spread); Vector2 rot = Vector2(Math::cos(angle1_rad), Math::sin(angle1_rad)); - p.velocity = rot * parameters[PARAM_INITIAL_LINEAR_VELOCITY] * Math::lerp((real_t)1.0, real_t(Math::randf()), randomness[PARAM_INITIAL_LINEAR_VELOCITY]); + p.velocity = rot * Math::lerp(parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], Math::randf()); - real_t base_angle = (parameters[PARAM_ANGLE] + tex_angle) * Math::lerp((real_t)1.0, p.angle_rand, randomness[PARAM_ANGLE]); + 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); p.custom[0] = 0.0; // unused p.custom[1] = 0.0; // phase [0..1] - p.custom[2] = (parameters[PARAM_ANIM_OFFSET] + tex_anim_offset) * Math::lerp((real_t)1.0, p.anim_offset_rand, randomness[PARAM_ANIM_OFFSET]); //animation phase [0..1] + p.custom[2] = tex_anim_offset * Math::lerp(parameters_min[PARAM_ANIM_OFFSET], parameters_max[PARAM_ANIM_OFFSET], p.anim_offset_rand); p.custom[3] = 0.0; p.transform = Transform2D(); p.time = 0; @@ -772,51 +800,51 @@ void CPUParticles2D::_particles_process(float p_delta) { p.custom[1] = p.time / lifetime; tv = p.time / p.lifetime; - real_t tex_linear_velocity = 0.0; + real_t tex_linear_velocity = 1.0; if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { tex_linear_velocity = curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY]->interpolate(tv); } - real_t tex_orbit_velocity = 0.0; + real_t tex_orbit_velocity = 1.0; if (curve_parameters[PARAM_ORBIT_VELOCITY].is_valid()) { tex_orbit_velocity = curve_parameters[PARAM_ORBIT_VELOCITY]->interpolate(tv); } - real_t tex_angular_velocity = 0.0; + real_t tex_angular_velocity = 1.0; if (curve_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) { tex_angular_velocity = curve_parameters[PARAM_ANGULAR_VELOCITY]->interpolate(tv); } - real_t tex_linear_accel = 0.0; + real_t tex_linear_accel = 1.0; if (curve_parameters[PARAM_LINEAR_ACCEL].is_valid()) { tex_linear_accel = curve_parameters[PARAM_LINEAR_ACCEL]->interpolate(tv); } - real_t tex_tangential_accel = 0.0; + real_t tex_tangential_accel = 1.0; if (curve_parameters[PARAM_TANGENTIAL_ACCEL].is_valid()) { tex_tangential_accel = curve_parameters[PARAM_TANGENTIAL_ACCEL]->interpolate(tv); } - real_t tex_radial_accel = 0.0; + real_t tex_radial_accel = 1.0; if (curve_parameters[PARAM_RADIAL_ACCEL].is_valid()) { tex_radial_accel = curve_parameters[PARAM_RADIAL_ACCEL]->interpolate(tv); } - real_t tex_damping = 0.0; + real_t tex_damping = 1.0; if (curve_parameters[PARAM_DAMPING].is_valid()) { tex_damping = curve_parameters[PARAM_DAMPING]->interpolate(tv); } - real_t tex_angle = 0.0; + real_t tex_angle = 1.0; if (curve_parameters[PARAM_ANGLE].is_valid()) { tex_angle = curve_parameters[PARAM_ANGLE]->interpolate(tv); } - real_t tex_anim_speed = 0.0; + real_t tex_anim_speed = 1.0; if (curve_parameters[PARAM_ANIM_SPEED].is_valid()) { tex_anim_speed = curve_parameters[PARAM_ANIM_SPEED]->interpolate(tv); } - real_t tex_anim_offset = 0.0; + real_t tex_anim_offset = 1.0; if (curve_parameters[PARAM_ANIM_OFFSET].is_valid()) { tex_anim_offset = curve_parameters[PARAM_ANIM_OFFSET]->interpolate(tv); } @@ -825,18 +853,18 @@ void CPUParticles2D::_particles_process(float p_delta) { Vector2 pos = p.transform[2]; //apply linear acceleration - force += p.velocity.length() > 0.0 ? p.velocity.normalized() * (parameters[PARAM_LINEAR_ACCEL] + tex_linear_accel) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_LINEAR_ACCEL]) : Vector2(); + force += p.velocity.length() > 0.0 ? p.velocity.normalized() * tex_linear_accel * Math::lerp(parameters_min[PARAM_LINEAR_ACCEL], parameters_max[PARAM_LINEAR_ACCEL], rand_from_seed(alt_seed)) : Vector2(); //apply radial acceleration Vector2 org = emission_xform[2]; Vector2 diff = pos - org; - force += diff.length() > 0.0 ? diff.normalized() * (parameters[PARAM_RADIAL_ACCEL] + tex_radial_accel) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_RADIAL_ACCEL]) : Vector2(); + force += diff.length() > 0.0 ? diff.normalized() * (tex_radial_accel)*Math::lerp(parameters_min[PARAM_RADIAL_ACCEL], parameters_max[PARAM_RADIAL_ACCEL], rand_from_seed(alt_seed)) : Vector2(); //apply tangential acceleration; Vector2 yx = Vector2(diff.y, diff.x); - force += yx.length() > 0.0 ? (yx * Vector2(-1.0, 1.0)).normalized() * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector2(); + force += yx.length() > 0.0 ? yx.normalized() * (tex_tangential_accel * Math::lerp(parameters_min[PARAM_TANGENTIAL_ACCEL], parameters_max[PARAM_TANGENTIAL_ACCEL], rand_from_seed(alt_seed))) : Vector2(); //apply attractor forces p.velocity += force * local_delta; //orbit velocity - real_t orbit_amount = (parameters[PARAM_ORBIT_VELOCITY] + tex_orbit_velocity) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_ORBIT_VELOCITY]); + real_t orbit_amount = tex_orbit_velocity * Math::lerp(parameters_min[PARAM_ORBIT_VELOCITY], parameters_max[PARAM_ORBIT_VELOCITY], rand_from_seed(alt_seed)); if (orbit_amount != 0.0) { real_t ang = orbit_amount * local_delta * Math_TAU; // Not sure why the ParticlesMaterial code uses a clockwise rotation matrix, @@ -849,9 +877,9 @@ void CPUParticles2D::_particles_process(float p_delta) { p.velocity = p.velocity.normalized() * tex_linear_velocity; } - if (parameters[PARAM_DAMPING] + tex_damping > 0.0) { + if (parameters_max[PARAM_DAMPING] + tex_damping > 0.0) { real_t v = p.velocity.length(); - real_t damp = (parameters[PARAM_DAMPING] + tex_damping) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_DAMPING]); + real_t damp = tex_damping * Math::lerp(parameters_min[PARAM_DAMPING], parameters_max[PARAM_DAMPING], rand_from_seed(alt_seed)); v -= damp * local_delta; if (v < 0.0) { p.velocity = Vector2(); @@ -859,18 +887,32 @@ void CPUParticles2D::_particles_process(float p_delta) { p.velocity = p.velocity.normalized() * v; } } - real_t base_angle = (parameters[PARAM_ANGLE] + tex_angle) * Math::lerp((real_t)1.0, p.angle_rand, randomness[PARAM_ANGLE]); - base_angle += p.custom[1] * lifetime * (parameters[PARAM_ANGULAR_VELOCITY] + tex_angular_velocity) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed) * 2.0f - 1.0f, randomness[PARAM_ANGULAR_VELOCITY]); + real_t base_angle = (tex_angle)*Math::lerp(parameters_min[PARAM_ANGLE], parameters_max[PARAM_ANGLE], p.angle_rand); + base_angle += p.custom[1] * lifetime * tex_angular_velocity * Math::lerp(parameters_min[PARAM_ANGULAR_VELOCITY], parameters_max[PARAM_ANGULAR_VELOCITY], rand_from_seed(alt_seed)); p.rotation = Math::deg2rad(base_angle); //angle - real_t animation_phase = (parameters[PARAM_ANIM_OFFSET] + tex_anim_offset) * Math::lerp((real_t)1.0, p.anim_offset_rand, randomness[PARAM_ANIM_OFFSET]) + p.custom[1] * (parameters[PARAM_ANIM_SPEED] + tex_anim_speed) * Math::lerp((real_t)1.0, rand_from_seed(alt_seed), randomness[PARAM_ANIM_SPEED]); - p.custom[2] = animation_phase; + p.custom[2] = tex_anim_offset * Math::lerp(parameters_min[PARAM_ANIM_OFFSET], parameters_max[PARAM_ANIM_OFFSET], p.anim_offset_rand) + p.custom[1] * tex_anim_speed * Math::lerp(parameters_min[PARAM_ANIM_SPEED], parameters_max[PARAM_ANIM_SPEED], rand_from_seed(alt_seed)); } //apply color //apply hue rotation - real_t tex_scale = 1.0; - if (curve_parameters[PARAM_SCALE].is_valid()) { - tex_scale = curve_parameters[PARAM_SCALE]->interpolate(tv); + Vector2 tex_scale = Vector2(1.0, 1.0); + if (split_scale) { + if (scale_curve_x.is_valid()) { + tex_scale.x = scale_curve_x->interpolate(tv); + } else { + tex_scale.x = 1.0; + } + if (scale_curve_y.is_valid()) { + tex_scale.y = scale_curve_y->interpolate(tv); + } else { + tex_scale.y = 1.0; + } + } else { + if (curve_parameters[PARAM_SCALE].is_valid()) { + real_t tmp_scale = curve_parameters[PARAM_SCALE]->interpolate(tv); + tex_scale.x = tmp_scale; + tex_scale.y = tmp_scale; + } } real_t tex_hue_variation = 0.0; @@ -878,7 +920,7 @@ void CPUParticles2D::_particles_process(float p_delta) { tex_hue_variation = curve_parameters[PARAM_HUE_VARIATION]->interpolate(tv); } - real_t hue_rot_angle = (parameters[PARAM_HUE_VARIATION] + tex_hue_variation) * Math_TAU * Math::lerp(1.0f, p.hue_rot_rand * 2.0f - 1.0f, randomness[PARAM_HUE_VARIATION]); + real_t hue_rot_angle = (tex_hue_variation)*Math_TAU * Math::lerp(parameters_min[PARAM_HUE_VARIATION], parameters_max[PARAM_HUE_VARIATION], p.hue_rot_rand); real_t hue_rot_c = Math::cos(hue_rot_angle); real_t hue_rot_s = Math::sin(hue_rot_angle); @@ -918,13 +960,15 @@ void CPUParticles2D::_particles_process(float p_delta) { } //scale by scale - real_t base_scale = tex_scale * Math::lerp(parameters[PARAM_SCALE], (real_t)1.0, p.scale_rand * randomness[PARAM_SCALE]); - if (base_scale < 0.000001) { - base_scale = 0.000001; + Vector2 base_scale = tex_scale * Math::lerp(parameters_min[PARAM_SCALE], parameters_max[PARAM_SCALE], p.scale_rand); + if (base_scale.x < 0.00001) { + base_scale.x = 0.00001; } - - p.transform.elements[0] *= base_scale; - p.transform.elements[1] *= base_scale; + 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[2] += p.velocity * local_delta; } @@ -1136,18 +1180,24 @@ void CPUParticles2D::convert_from_particles(Node *p_particles) { Vector2 rect_extents = Vector2(material->get_emission_box_extents().x, material->get_emission_box_extents().y); set_emission_rect_extents(rect_extents); + Ref<CurveXYZTexture> scale3D = material->get_param_texture(ParticlesMaterial::PARAM_SCALE); + if (scale3D.is_valid()) { + split_scale = true; + scale_curve_x = scale3D->get_curve_x(); + scale_curve_y = scale3D->get_curve_y(); + } Vector2 gravity = Vector2(material->get_gravity().x, material->get_gravity().y); set_gravity(gravity); set_lifetime_randomness(material->get_lifetime_randomness()); #define CONVERT_PARAM(m_param) \ - set_param(m_param, material->get_param(ParticlesMaterial::m_param)); \ + set_param_min(m_param, material->get_param_min(ParticlesMaterial::m_param)); \ { \ Ref<CurveTexture> ctex = material->get_param_texture(ParticlesMaterial::m_param); \ if (ctex.is_valid()) \ set_param_curve(m_param, ctex->get_curve()); \ } \ - set_param_randomness(m_param, material->get_param_randomness(ParticlesMaterial::m_param)); + set_param_max(m_param, material->get_param_max(ParticlesMaterial::m_param)); CONVERT_PARAM(PARAM_INITIAL_LINEAR_VELOCITY); CONVERT_PARAM(PARAM_ANGULAR_VELOCITY); @@ -1230,11 +1280,11 @@ void CPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_spread", "degrees"), &CPUParticles2D::set_spread); ClassDB::bind_method(D_METHOD("get_spread"), &CPUParticles2D::get_spread); - ClassDB::bind_method(D_METHOD("set_param", "param", "value"), &CPUParticles2D::set_param); - ClassDB::bind_method(D_METHOD("get_param", "param"), &CPUParticles2D::get_param); + ClassDB::bind_method(D_METHOD("set_param_min", "param", "value"), &CPUParticles2D::set_param_min); + ClassDB::bind_method(D_METHOD("get_param_min", "param"), &CPUParticles2D::get_param_min); - ClassDB::bind_method(D_METHOD("set_param_randomness", "param", "randomness"), &CPUParticles2D::set_param_randomness); - ClassDB::bind_method(D_METHOD("get_param_randomness", "param"), &CPUParticles2D::get_param_randomness); + ClassDB::bind_method(D_METHOD("set_param_max", "param", "value"), &CPUParticles2D::set_param_max); + ClassDB::bind_method(D_METHOD("get_param_max", "param"), &CPUParticles2D::get_param_max); ClassDB::bind_method(D_METHOD("set_param_curve", "param", "curve"), &CPUParticles2D::set_param_curve); ClassDB::bind_method(D_METHOD("get_param_curve", "param"), &CPUParticles2D::get_param_curve); @@ -1269,6 +1319,15 @@ void CPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_gravity"), &CPUParticles2D::get_gravity); ClassDB::bind_method(D_METHOD("set_gravity", "accel_vec"), &CPUParticles2D::set_gravity); + ClassDB::bind_method(D_METHOD("get_split_scale"), &CPUParticles2D::get_split_scale); + ClassDB::bind_method(D_METHOD("set_split_scale", "split_scale"), &CPUParticles2D::set_split_scale); + + ClassDB::bind_method(D_METHOD("get_scale_curve_x"), &CPUParticles2D::get_scale_curve_x); + ClassDB::bind_method(D_METHOD("set_scale_curve_x", "scale_curve"), &CPUParticles2D::set_scale_curve_x); + + ClassDB::bind_method(D_METHOD("get_scale_curve_y"), &CPUParticles2D::get_scale_curve_y); + ClassDB::bind_method(D_METHOD("set_scale_curve_y", "scale_curve"), &CPUParticles2D::set_scale_curve_y); + ClassDB::bind_method(D_METHOD("convert_from_particles", "particles"), &CPUParticles2D::convert_from_particles); ADD_GROUP("Emission Shape", "emission_"); @@ -1286,54 +1345,58 @@ void CPUParticles2D::_bind_methods() { ADD_GROUP("Gravity", ""); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "gravity"), "set_gravity", "get_gravity"); ADD_GROUP("Initial Velocity", "initial_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param", "get_param", PARAM_INITIAL_LINEAR_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_INITIAL_LINEAR_VELOCITY); + 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_GROUP("Angular Velocity", "angular_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ANGULAR_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGULAR_VELOCITY); + 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); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angular_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANGULAR_VELOCITY); ADD_GROUP("Orbit Velocity", "orbit_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ORBIT_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ORBIT_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_min", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_ORBIT_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_max", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_ORBIT_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "orbit_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ORBIT_VELOCITY); ADD_GROUP("Linear Accel", "linear_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_LINEAR_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_LINEAR_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_LINEAR_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_LINEAR_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "linear_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_LINEAR_ACCEL); ADD_GROUP("Radial Accel", "radial_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_RADIAL_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_RADIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_RADIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_RADIAL_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "radial_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_RADIAL_ACCEL); ADD_GROUP("Tangential Accel", "tangential_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_TANGENTIAL_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_TANGENTIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_TANGENTIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_TANGENTIAL_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "tangential_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_TANGENTIAL_ACCEL); ADD_GROUP("Damping", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_param", "get_param", PARAM_DAMPING); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_min", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_param_min", "get_param_min", PARAM_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_max", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_param_max", "get_param_max", PARAM_DAMPING); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_DAMPING); ADD_GROUP("Angle", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param", "get_param", PARAM_ANGLE); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGLE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_min", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param_min", "get_param_min", PARAM_ANGLE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_max", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param_max", "get_param_max", PARAM_ANGLE); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angle_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANGLE); ADD_GROUP("Scale", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_amount", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param", "get_param", PARAM_SCALE); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_amount_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_SCALE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_amount_min", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_min", "get_param_min", PARAM_SCALE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_amount_max", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_max", "get_param_max", PARAM_SCALE); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "scale_amount_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_SCALE); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "split_scale"), "set_split_scale", "get_split_scale"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "scale_curve_x", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_scale_curve_x", "get_scale_curve_x"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "scale_curve_y", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_scale_curve_y", "get_scale_curve_y"); + ADD_GROUP("Color", ""); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "color_ramp", PROPERTY_HINT_RESOURCE_TYPE, "Gradient"), "set_color_ramp", "get_color_ramp"); ADD_GROUP("Hue Variation", "hue_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param", "get_param", PARAM_HUE_VARIATION); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_HUE_VARIATION); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_min", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param_min", "get_param_min", PARAM_HUE_VARIATION); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_max", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param_max", "get_param_max", PARAM_HUE_VARIATION); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "hue_variation_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_HUE_VARIATION); ADD_GROUP("Animation", "anim_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater"), "set_param", "get_param", PARAM_ANIM_SPEED); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANIM_SPEED); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_min", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater,or_lesser"), "set_param_min", "get_param_min", PARAM_ANIM_SPEED); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_max", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater,or_lesser"), "set_param_max", "get_param_max", PARAM_ANIM_SPEED); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_speed_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANIM_SPEED); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_ANIM_OFFSET); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANIM_OFFSET); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_min", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_min", "get_param_min", PARAM_ANIM_OFFSET); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_max", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_max", "get_param_max", PARAM_ANIM_OFFSET); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_offset_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANIM_OFFSET); BIND_ENUM_CONSTANT(PARAM_INITIAL_LINEAR_VELOCITY); @@ -1372,22 +1435,31 @@ CPUParticles2D::CPUParticles2D() { set_amount(8); set_use_local_coordinates(true); - set_param(PARAM_INITIAL_LINEAR_VELOCITY, 0); - set_param(PARAM_ANGULAR_VELOCITY, 0); - set_param(PARAM_ORBIT_VELOCITY, 0); - set_param(PARAM_LINEAR_ACCEL, 0); - set_param(PARAM_RADIAL_ACCEL, 0); - set_param(PARAM_TANGENTIAL_ACCEL, 0); - set_param(PARAM_DAMPING, 0); - set_param(PARAM_ANGLE, 0); - set_param(PARAM_SCALE, 1); - set_param(PARAM_HUE_VARIATION, 0); - set_param(PARAM_ANIM_SPEED, 0); - set_param(PARAM_ANIM_OFFSET, 0); - - for (int i = 0; i < PARAM_MAX; i++) { - set_param_randomness(Parameter(i), 0); - } + set_param_min(PARAM_INITIAL_LINEAR_VELOCITY, 0); + set_param_min(PARAM_ANGULAR_VELOCITY, 0); + set_param_min(PARAM_ORBIT_VELOCITY, 0); + set_param_min(PARAM_LINEAR_ACCEL, 0); + set_param_min(PARAM_RADIAL_ACCEL, 0); + set_param_min(PARAM_TANGENTIAL_ACCEL, 0); + set_param_min(PARAM_DAMPING, 0); + set_param_min(PARAM_ANGLE, 0); + set_param_min(PARAM_SCALE, 1); + set_param_min(PARAM_HUE_VARIATION, 0); + set_param_min(PARAM_ANIM_SPEED, 0); + set_param_min(PARAM_ANIM_OFFSET, 0); + + set_param_max(PARAM_INITIAL_LINEAR_VELOCITY, 0); + set_param_max(PARAM_ANGULAR_VELOCITY, 0); + set_param_max(PARAM_ORBIT_VELOCITY, 0); + set_param_max(PARAM_LINEAR_ACCEL, 0); + set_param_max(PARAM_RADIAL_ACCEL, 0); + set_param_max(PARAM_TANGENTIAL_ACCEL, 0); + set_param_max(PARAM_DAMPING, 0); + set_param_max(PARAM_ANGLE, 0); + set_param_max(PARAM_SCALE, 1); + set_param_max(PARAM_HUE_VARIATION, 0); + set_param_max(PARAM_ANIM_SPEED, 0); + set_param_max(PARAM_ANIM_OFFSET, 0); for (int i = 0; i < PARTICLE_FLAG_MAX; i++) { particle_flags[i] = false; diff --git a/scene/2d/cpu_particles_2d.h b/scene/2d/cpu_particles_2d.h index 92b8be77cf..4990d443e3 100644 --- a/scene/2d/cpu_particles_2d.h +++ b/scene/2d/cpu_particles_2d.h @@ -31,9 +31,7 @@ #ifndef CPU_PARTICLES_2D_H #define CPU_PARTICLES_2D_H -#include "core/templates/rid.h" #include "scene/2d/node_2d.h" -#include "scene/resources/texture.h" class CPUParticles2D : public Node2D { private: @@ -83,7 +81,7 @@ private: struct Particle { Transform2D transform; Color color; - float custom[4] = {}; + real_t custom[4] = {}; real_t rotation = 0.0; Vector2 velocity; bool active = false; @@ -91,16 +89,16 @@ private: real_t scale_rand = 0.0; real_t hue_rot_rand = 0.0; real_t anim_offset_rand = 0.0; - float time = 0.0; - float lifetime = 0.0; + double time = 0.0; + double lifetime = 0.0; Color base_color; uint32_t seed = 0; }; - float time = 0.0; - float inactive_time = 0.0; - float frame_remainder = 0.0; + double time = 0.0; + double inactive_time = 0.0; + double frame_remainder = 0.0; int cycle = 0; bool redraw = false; @@ -131,12 +129,12 @@ private: bool one_shot = false; - float lifetime = 1.0; - float pre_process_time = 0.0; + double lifetime = 1.0; + double pre_process_time = 0.0; real_t explosiveness_ratio = 0.0; real_t randomness_ratio = 0.0; - real_t lifetime_randomness = 0.0; - real_t speed_scale = 1.0; + double lifetime_randomness = 0.0; + double speed_scale = 1.0; bool local_coords; int fixed_fps = 0; bool fractional_delta = true; @@ -152,8 +150,8 @@ private: Vector2 direction = Vector2(1, 0); real_t spread = 45.0; - real_t parameters[PARAM_MAX]; - real_t randomness[PARAM_MAX]; + real_t parameters_min[PARAM_MAX]; + real_t parameters_max[PARAM_MAX]; Ref<Curve> curve_parameters[PARAM_MAX]; Color color; @@ -169,10 +167,14 @@ private: Vector<Color> emission_colors; int emission_point_count = 0; + Ref<Curve> scale_curve_x; + Ref<Curve> scale_curve_y; + bool split_scale = false; + Vector2 gravity = Vector2(0, 980); void _update_internal(); - void _particles_process(float p_delta); + void _particles_process(double p_delta); void _update_particle_data_buffer(); Mutex update_mutex; @@ -193,27 +195,27 @@ protected: public: void set_emitting(bool p_emitting); void set_amount(int p_amount); - void set_lifetime(float p_lifetime); + void set_lifetime(double p_lifetime); void set_one_shot(bool p_one_shot); - void set_pre_process_time(float p_time); + void set_pre_process_time(double p_time); void set_explosiveness_ratio(real_t p_ratio); void set_randomness_ratio(real_t p_ratio); - void set_lifetime_randomness(float p_random); + void set_lifetime_randomness(double p_random); void set_visibility_aabb(const Rect2 &p_aabb); void set_use_local_coordinates(bool p_enable); - void set_speed_scale(real_t p_scale); + void set_speed_scale(double p_scale); bool is_emitting() const; int get_amount() const; - float get_lifetime() const; + double get_lifetime() const; bool get_one_shot() const; - float get_pre_process_time() const; + double get_pre_process_time() const; real_t get_explosiveness_ratio() const; real_t get_randomness_ratio() const; - float get_lifetime_randomness() const; + double get_lifetime_randomness() const; Rect2 get_visibility_aabb() const; bool get_use_local_coordinates() const; - real_t get_speed_scale() const; + double get_speed_scale() const; void set_fixed_fps(int p_count); int get_fixed_fps() const; @@ -238,11 +240,11 @@ public: void set_spread(real_t p_spread); real_t get_spread() const; - void set_param(Parameter p_param, real_t p_value); - real_t get_param(Parameter p_param) const; + void set_param_min(Parameter p_param, real_t p_value); + real_t get_param_min(Parameter p_param) const; - void set_param_randomness(Parameter p_param, real_t p_value); - real_t get_param_randomness(Parameter p_param) const; + void set_param_max(Parameter p_param, real_t p_value); + real_t get_param_max(Parameter p_param) const; void set_param_curve(Parameter p_param, const Ref<Curve> &p_curve); Ref<Curve> get_param_curve(Parameter p_param) const; @@ -263,6 +265,9 @@ public: void set_emission_normals(const Vector<Vector2> &p_normals); void set_emission_colors(const Vector<Color> &p_colors); void set_emission_point_count(int p_count); + void set_scale_curve_x(Ref<Curve> p_scale_curve); + void set_scale_curve_y(Ref<Curve> p_scale_curve); + void set_split_scale(bool p_split_scale); EmissionShape get_emission_shape() const; real_t get_emission_sphere_radius() const; @@ -271,6 +276,9 @@ public: Vector<Vector2> get_emission_normals() const; Vector<Color> get_emission_colors() const; int get_emission_point_count() const; + Ref<Curve> get_scale_curve_x() const; + Ref<Curve> get_scale_curve_y() const; + bool get_split_scale(); void set_gravity(const Vector2 &p_gravity); Vector2 get_gravity() const; diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp index adfb94d574..5bce705dd5 100644 --- a/scene/2d/gpu_particles_2d.cpp +++ b/scene/2d/gpu_particles_2d.cpp @@ -30,9 +30,7 @@ #include "gpu_particles_2d.h" -#include "core/os/os.h" #include "scene/resources/particles_material.h" -#include "scene/scene_string_names.h" #ifdef TOOLS_ENABLED #include "core/config/engine.h" @@ -54,7 +52,7 @@ void GPUParticles2D::set_amount(int p_amount) { RS::get_singleton()->particles_set_amount(particles, amount); } -void GPUParticles2D::set_lifetime(float p_lifetime) { +void GPUParticles2D::set_lifetime(double p_lifetime) { ERR_FAIL_COND_MSG(p_lifetime <= 0, "Particles lifetime must be greater than 0."); lifetime = p_lifetime; RS::get_singleton()->particles_set_lifetime(particles, lifetime); @@ -76,17 +74,17 @@ void GPUParticles2D::set_one_shot(bool p_enable) { } } -void GPUParticles2D::set_pre_process_time(float p_time) { +void GPUParticles2D::set_pre_process_time(double p_time) { pre_process_time = p_time; RS::get_singleton()->particles_set_pre_process_time(particles, pre_process_time); } -void GPUParticles2D::set_explosiveness_ratio(float p_ratio) { +void GPUParticles2D::set_explosiveness_ratio(real_t p_ratio) { explosiveness_ratio = p_ratio; RS::get_singleton()->particles_set_explosiveness_ratio(particles, explosiveness_ratio); } -void GPUParticles2D::set_randomness_ratio(float p_ratio) { +void GPUParticles2D::set_randomness_ratio(real_t p_ratio) { randomness_ratio = p_ratio; RS::get_singleton()->particles_set_randomness_ratio(particles, randomness_ratio); } @@ -148,7 +146,8 @@ void GPUParticles2D::set_trail_enabled(bool p_enabled) { RS::get_singleton()->particles_set_transform_align(particles, p_enabled ? RS::PARTICLES_TRANSFORM_ALIGN_Y_TO_VELOCITY : RS::PARTICLES_TRANSFORM_ALIGN_DISABLED); } -void GPUParticles2D::set_trail_length(float p_seconds) { + +void GPUParticles2D::set_trail_length(double p_seconds) { ERR_FAIL_COND(p_seconds < 0.001); trail_length = p_seconds; RS::get_singleton()->particles_set_trails(particles, trail_enabled, trail_length); @@ -162,6 +161,7 @@ void GPUParticles2D::set_trail_sections(int p_sections) { trail_sections = p_sections; update(); } + void GPUParticles2D::set_trail_section_subdivisions(int p_subdivisions) { ERR_FAIL_COND(trail_section_subdivisions < 1); ERR_FAIL_COND(trail_section_subdivisions > 1024); @@ -173,12 +173,13 @@ void GPUParticles2D::set_trail_section_subdivisions(int p_subdivisions) { bool GPUParticles2D::is_trail_enabled() const { return trail_enabled; } -float GPUParticles2D::get_trail_length() const { + +double GPUParticles2D::get_trail_length() const { return trail_length; } void GPUParticles2D::_update_collision_size() { - float csize = collision_base_size; + real_t csize = collision_base_size; if (texture.is_valid()) { csize *= (texture->get_width() + texture->get_height()) / 4.0; //half size since its a radius @@ -187,16 +188,16 @@ void GPUParticles2D::_update_collision_size() { RS::get_singleton()->particles_set_collision_base_size(particles, csize); } -void GPUParticles2D::set_collision_base_size(float p_size) { +void GPUParticles2D::set_collision_base_size(real_t p_size) { collision_base_size = p_size; _update_collision_size(); } -float GPUParticles2D::get_collision_base_size() const { +real_t GPUParticles2D::get_collision_base_size() const { return collision_base_size; } -void GPUParticles2D::set_speed_scale(float p_scale) { +void GPUParticles2D::set_speed_scale(double p_scale) { speed_scale = p_scale; RS::get_singleton()->particles_set_speed_scale(particles, p_scale); } @@ -209,7 +210,7 @@ int GPUParticles2D::get_amount() const { return amount; } -float GPUParticles2D::get_lifetime() const { +double GPUParticles2D::get_lifetime() const { return lifetime; } @@ -224,15 +225,15 @@ bool GPUParticles2D::get_one_shot() const { return one_shot; } -float GPUParticles2D::get_pre_process_time() const { +double GPUParticles2D::get_pre_process_time() const { return pre_process_time; } -float GPUParticles2D::get_explosiveness_ratio() const { +real_t GPUParticles2D::get_explosiveness_ratio() const { return explosiveness_ratio; } -float GPUParticles2D::get_randomness_ratio() const { +real_t GPUParticles2D::get_randomness_ratio() const { return randomness_ratio; } @@ -248,7 +249,7 @@ Ref<Material> GPUParticles2D::get_process_material() const { return process_material; } -float GPUParticles2D::get_speed_scale() const { +double GPUParticles2D::get_speed_scale() const { return speed_scale; } @@ -294,7 +295,7 @@ TypedArray<String> GPUParticles2D::get_configuration_warnings() const { if (get_material().is_null() || (mat && !mat->get_particles_animation())) { const ParticlesMaterial *process = Object::cast_to<ParticlesMaterial>(process_material.ptr()); if (process && - (process->get_param(ParticlesMaterial::PARAM_ANIM_SPEED) != 0.0 || process->get_param(ParticlesMaterial::PARAM_ANIM_OFFSET) != 0.0 || + (process->get_param_max(ParticlesMaterial::PARAM_ANIM_SPEED) != 0.0 || process->get_param_max(ParticlesMaterial::PARAM_ANIM_OFFSET) != 0.0 || process->get_param_texture(ParticlesMaterial::PARAM_ANIM_SPEED).is_valid() || process->get_param_texture(ParticlesMaterial::PARAM_ANIM_OFFSET).is_valid())) { warnings.push_back(TTR("Particles2D animation requires the usage of a CanvasItemMaterial with \"Particles Animation\" enabled.")); } @@ -352,19 +353,19 @@ void GPUParticles2D::_notification(int p_what) { PackedInt32Array indices; int total_segments = trail_sections * trail_section_subdivisions; - float depth = size.height * trail_sections; + real_t depth = size.height * trail_sections; for (int j = 0; j <= total_segments; j++) { - float v = j; + real_t v = j; v /= total_segments; - float y = depth * v; + real_t y = depth * v; y = (depth * 0.5) - y; int bone = j / trail_section_subdivisions; - float blend = 1.0 - float(j % trail_section_subdivisions) / float(trail_section_subdivisions); + real_t blend = 1.0 - real_t(j % trail_section_subdivisions) / real_t(trail_section_subdivisions); - float s = size.width; + real_t s = size.width; points.push_back(Vector2(-s * 0.5, 0)); points.push_back(Vector2(+s * 0.5, 0)); @@ -410,7 +411,7 @@ void GPUParticles2D::_notification(int p_what) { for (int i = 0; i <= trail_sections; i++) { Transform3D xform; /* - xform.origin.y = depth / 2.0 - size.height * float(i); + xform.origin.y = depth / 2.0 - size.height * real_t(i); xform.origin.y = -xform.origin.y; //bind is an inverse transform, so negate y */ xforms.push_back(xform); } diff --git a/scene/2d/gpu_particles_2d.h b/scene/2d/gpu_particles_2d.h index 9d8e61daf7..d7eee461b4 100644 --- a/scene/2d/gpu_particles_2d.h +++ b/scene/2d/gpu_particles_2d.h @@ -31,9 +31,7 @@ #ifndef PARTICLES_2D_H #define PARTICLES_2D_H -#include "core/templates/rid.h" #include "scene/2d/node_2d.h" -#include "scene/resources/texture.h" class GPUParticles2D : public Node2D { private: @@ -51,11 +49,11 @@ private: bool one_shot; int amount; - float lifetime; - float pre_process_time; - float explosiveness_ratio; - float randomness_ratio; - float speed_scale; + double lifetime; + double pre_process_time; + real_t explosiveness_ratio; + real_t randomness_ratio; + double speed_scale; Rect2 visibility_rect; bool local_coords; int fixed_fps; @@ -70,10 +68,10 @@ private: void _update_particle_emission_transform(); NodePath sub_emitter; - float collision_base_size = 1.0; + real_t collision_base_size = 1.0; bool trail_enabled = false; - float trail_length = 0.3; + double trail_length = 0.3; int trail_sections = 8; int trail_section_subdivisions = 4; @@ -89,36 +87,36 @@ protected: public: void set_emitting(bool p_emitting); void set_amount(int p_amount); - void set_lifetime(float p_lifetime); + void set_lifetime(double p_lifetime); void set_one_shot(bool p_enable); - void set_pre_process_time(float p_time); - void set_explosiveness_ratio(float p_ratio); - void set_randomness_ratio(float p_ratio); + void set_pre_process_time(double p_time); + void set_explosiveness_ratio(real_t p_ratio); + void set_randomness_ratio(real_t p_ratio); void set_visibility_rect(const Rect2 &p_visibility_rect); void set_use_local_coordinates(bool p_enable); void set_process_material(const Ref<Material> &p_material); - void set_speed_scale(float p_scale); - void set_collision_base_size(float p_ratio); + void set_speed_scale(double p_scale); + void set_collision_base_size(real_t p_ratio); void set_trail_enabled(bool p_enabled); - void set_trail_length(float p_seconds); + void set_trail_length(double p_seconds); void set_trail_sections(int p_sections); void set_trail_section_subdivisions(int p_subdivisions); bool is_emitting() const; int get_amount() const; - float get_lifetime() const; + double get_lifetime() const; bool get_one_shot() const; - float get_pre_process_time() const; - float get_explosiveness_ratio() const; - float get_randomness_ratio() const; + double get_pre_process_time() const; + real_t get_explosiveness_ratio() const; + real_t get_randomness_ratio() const; Rect2 get_visibility_rect() const; bool get_use_local_coordinates() const; Ref<Material> get_process_material() const; - float get_speed_scale() const; + double get_speed_scale() const; - float get_collision_base_size() const; + real_t get_collision_base_size() const; bool is_trail_enabled() const; - float get_trail_length() const; + double get_trail_length() const; int get_trail_sections() const; int get_trail_section_subdivisions() const; diff --git a/scene/2d/joints_2d.cpp b/scene/2d/joints_2d.cpp index dbba6917b5..4a6606256e 100644 --- a/scene/2d/joints_2d.cpp +++ b/scene/2d/joints_2d.cpp @@ -30,10 +30,8 @@ #include "joints_2d.h" -#include "core/config/engine.h" #include "physics_body_2d.h" #include "scene/scene_string_names.h" -#include "servers/physics_server_2d.h" void Joint2D::_disconnect_signals() { Node *node_a = get_node_or_null(a); @@ -254,7 +252,7 @@ void PinJoint2D::_notification(int p_what) { } void PinJoint2D::_configure_joint(RID p_joint, PhysicsBody2D *body_a, PhysicsBody2D *body_b) { - PhysicsServer2D::get_singleton()->joint_make_pin(p_joint, get_global_transform().get_origin(), body_a->get_rid(), body_b ? body_b->get_rid() : RID()); + PhysicsServer2D::get_singleton()->joint_make_pin(p_joint, get_global_position(), body_a->get_rid(), body_b ? body_b->get_rid() : RID()); PhysicsServer2D::get_singleton()->pin_joint_set_param(p_joint, PhysicsServer2D::PIN_JOINT_SOFTNESS, softness); } diff --git a/scene/2d/light_2d.cpp b/scene/2d/light_2d.cpp index ce57895341..1853b3428c 100644 --- a/scene/2d/light_2d.cpp +++ b/scene/2d/light_2d.cpp @@ -30,9 +30,6 @@ #include "light_2d.h" -#include "core/config/engine.h" -#include "servers/rendering_server.h" - void Light2D::_update_light_visibility() { if (!is_inside_tree()) { return; diff --git a/scene/2d/line_builder.cpp b/scene/2d/line_builder.cpp index c478f03356..a8a2639ccf 100644 --- a/scene/2d/line_builder.cpp +++ b/scene/2d/line_builder.cpp @@ -62,14 +62,6 @@ static SegmentIntersectionResult segment_intersection( return SEGMENT_PARALLEL; } -// TODO I'm pretty sure there is an even faster way to swap things -template <typename T> -static inline void swap(T &a, T &b) { - T tmp = a; - a = b; - b = tmp; -} - static float calculate_total_distance(const Vector<Vector2> &points) { float d = 0.f; for (int i = 1; i < points.size(); ++i) { diff --git a/scene/2d/line_builder.h b/scene/2d/line_builder.h index 654e61422b..16c88d00e9 100644 --- a/scene/2d/line_builder.h +++ b/scene/2d/line_builder.h @@ -31,10 +31,7 @@ #ifndef LINE_BUILDER_H #define LINE_BUILDER_H -#include "core/math/color.h" -#include "core/math/vector2.h" #include "line_2d.h" -#include "scene/resources/gradient.h" class LineBuilder { public: diff --git a/scene/gui/shortcut.cpp b/scene/2d/listener_2d.cpp index 962c6dcc60..444f05f2b1 100644 --- a/scene/gui/shortcut.cpp +++ b/scene/2d/listener_2d.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* shortcut.cpp */ +/* listener_2d.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,46 +28,85 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "shortcut.h" +#include "listener_2d.h" -#include "core/os/keyboard.h" - -void Shortcut::set_shortcut(const Ref<InputEvent> &p_shortcut) { - shortcut = p_shortcut; - emit_changed(); +bool Listener2D::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "current") { + if (p_value.operator bool()) { + make_current(); + } else { + clear_current(); + } + } else { + return false; + } + return true; } -Ref<InputEvent> Shortcut::get_shortcut() const { - return shortcut; +bool Listener2D::_get(const StringName &p_name, Variant &r_ret) const { + if (p_name == "current") { + if (is_inside_tree() && get_tree()->is_node_being_edited(this)) { + r_ret = current; + } else { + r_ret = is_current(); + } + } else { + return false; + } + return true; } -bool Shortcut::is_shortcut(const Ref<InputEvent> &p_event) const { - return shortcut.is_valid() && shortcut->is_match(p_event, true); +void Listener2D::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back(PropertyInfo(Variant::BOOL, "current")); } -String Shortcut::get_as_text() const { - if (shortcut.is_valid()) { - return shortcut->as_text(); - } else { - return "None"; +void Listener2D::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + if (!get_tree()->is_node_being_edited(this) && current) { + make_current(); + } + } break; + case NOTIFICATION_EXIT_TREE: { + if (!get_tree()->is_node_being_edited(this)) { + if (is_current()) { + clear_current(); + current = true; // Keep it true. + } else { + current = false; + } + } + } break; } } -bool Shortcut::is_valid() const { - return shortcut.is_valid(); +void Listener2D::make_current() { + current = true; + if (!is_inside_tree()) { + return; + } + get_viewport()->_listener_2d_set(this); } -void Shortcut::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_shortcut", "event"), &Shortcut::set_shortcut); - ClassDB::bind_method(D_METHOD("get_shortcut"), &Shortcut::get_shortcut); - - ClassDB::bind_method(D_METHOD("is_valid"), &Shortcut::is_valid); - - ClassDB::bind_method(D_METHOD("is_shortcut", "event"), &Shortcut::is_shortcut); - ClassDB::bind_method(D_METHOD("get_as_text"), &Shortcut::get_as_text); +void Listener2D::clear_current() { + current = false; + if (!is_inside_tree()) { + return; + } + get_viewport()->_listener_2d_remove(this); +} - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"), "set_shortcut", "get_shortcut"); +bool Listener2D::is_current() const { + if (is_inside_tree() && !get_tree()->is_node_being_edited(this)) { + return get_viewport()->get_listener_2d() == this; + } else { + return current; + } + return false; } -Shortcut::Shortcut() { +void Listener2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("make_current"), &Listener2D::make_current); + ClassDB::bind_method(D_METHOD("clear_current"), &Listener2D::clear_current); + ClassDB::bind_method(D_METHOD("is_current"), &Listener2D::is_current); } diff --git a/scene/gui/shortcut.h b/scene/2d/listener_2d.h index ea91f29b5d..0289a8087d 100644 --- a/scene/gui/shortcut.h +++ b/scene/2d/listener_2d.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* shortcut.h */ +/* listener_2d.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,29 +28,34 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef SHORTCUT_H -#define SHORTCUT_H +#ifndef LISTENER_2D_H +#define LISTENER_2D_H -#include "core/input/input_event.h" -#include "core/io/resource.h" +#include "scene/2d/node_2d.h" +#include "scene/main/window.h" -class Shortcut : public Resource { - GDCLASS(Shortcut, Resource); +class Listener2D : public Node2D { + GDCLASS(Listener2D, Node2D); - Ref<InputEvent> shortcut; +private: + bool current = false; + + friend class Viewport; protected: - static void _bind_methods(); + void _update_listener(); -public: - void set_shortcut(const Ref<InputEvent> &p_shortcut); - Ref<InputEvent> get_shortcut() const; - bool is_shortcut(const Ref<InputEvent> &p_event) const; - bool is_valid() const; + 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 _notification(int p_what); - String get_as_text() const; + static void _bind_methods(); - Shortcut(); +public: + void make_current(); + void clear_current(); + bool is_current() const; }; -#endif // SHORTCUT_H +#endif diff --git a/scene/2d/navigation_agent_2d.cpp b/scene/2d/navigation_agent_2d.cpp index f9cbdbf377..2f00978123 100644 --- a/scene/2d/navigation_agent_2d.cpp +++ b/scene/2d/navigation_agent_2d.cpp @@ -30,7 +30,6 @@ #include "navigation_agent_2d.h" -#include "core/config/engine.h" #include "core/math/geometry_2d.h" #include "servers/navigation_server_2d.h" @@ -103,7 +102,7 @@ void NavigationAgent2D::_notification(int p_what) { } break; case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { if (agent_parent) { - NavigationServer2D::get_singleton()->agent_set_position(agent, agent_parent->get_global_transform().get_origin()); + NavigationServer2D::get_singleton()->agent_set_position(agent, agent_parent->get_global_position()); _check_distance_to_target(); } } break; @@ -186,7 +185,7 @@ Vector2 NavigationAgent2D::get_next_location() { update_navigation(); if (navigation_path.size() == 0) { ERR_FAIL_COND_V(agent_parent == nullptr, Vector2()); - return agent_parent->get_global_transform().get_origin(); + return agent_parent->get_global_position(); } else { return navigation_path[nav_path_index]; } @@ -194,7 +193,7 @@ Vector2 NavigationAgent2D::get_next_location() { real_t NavigationAgent2D::distance_to_target() const { ERR_FAIL_COND_V(agent_parent == nullptr, 0.0); - return agent_parent->get_global_transform().get_origin().distance_to(target_location); + return agent_parent->get_global_position().distance_to(target_location); } bool NavigationAgent2D::is_target_reached() const { @@ -235,7 +234,7 @@ void NavigationAgent2D::_avoidance_done(Vector3 p_new_velocity) { } velocity_submitted = false; - emit_signal("velocity_computed", velocity); + emit_signal(SNAME("velocity_computed"), velocity); } TypedArray<String> NavigationAgent2D::get_configuration_warnings() const { @@ -261,7 +260,7 @@ void NavigationAgent2D::update_navigation() { update_frame_id = Engine::get_singleton()->get_physics_frames(); - Vector2 o = agent_parent->get_global_transform().get_origin(); + Vector2 o = agent_parent->get_global_position(); bool reload_path = false; @@ -287,7 +286,7 @@ void NavigationAgent2D::update_navigation() { navigation_path = NavigationServer2D::get_singleton()->map_get_path(agent_parent->get_world_2d()->get_navigation_map(), o, target_location, true, navigable_layers); navigation_finished = false; nav_path_index = 0; - emit_signal("path_changed"); + emit_signal(SNAME("path_changed")); } if (navigation_path.size() == 0) { @@ -303,7 +302,7 @@ void NavigationAgent2D::update_navigation() { _check_distance_to_target(); nav_path_index -= 1; navigation_finished = true; - emit_signal("navigation_finished"); + emit_signal(SNAME("navigation_finished")); break; } } @@ -313,7 +312,7 @@ void NavigationAgent2D::update_navigation() { void NavigationAgent2D::_check_distance_to_target() { if (!target_reached) { if (distance_to_target() < target_desired_distance) { - emit_signal("target_reached"); + emit_signal(SNAME("target_reached")); target_reached = true; } } diff --git a/scene/2d/navigation_agent_2d.h b/scene/2d/navigation_agent_2d.h index 234cad333f..052cd78a56 100644 --- a/scene/2d/navigation_agent_2d.h +++ b/scene/2d/navigation_agent_2d.h @@ -31,7 +31,6 @@ #ifndef NAVIGATION_AGENT_2D_H #define NAVIGATION_AGENT_2D_H -#include "core/templates/vector.h" #include "scene/main/node.h" class Node2D; diff --git a/scene/2d/navigation_obstacle_2d.cpp b/scene/2d/navigation_obstacle_2d.cpp index a06f7a9fd0..0a105826c0 100644 --- a/scene/2d/navigation_obstacle_2d.cpp +++ b/scene/2d/navigation_obstacle_2d.cpp @@ -31,7 +31,6 @@ #include "navigation_obstacle_2d.h" #include "scene/2d/collision_shape_2d.h" -#include "scene/2d/physics_body_2d.h" #include "servers/navigation_server_2d.h" void NavigationObstacle2D::_bind_methods() { @@ -54,7 +53,7 @@ void NavigationObstacle2D::_notification(int p_what) { } break; case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { if (parent_node2d) { - NavigationServer2D::get_singleton()->agent_set_position(agent, parent_node2d->get_global_transform().get_origin()); + NavigationServer2D::get_singleton()->agent_set_position(agent, parent_node2d->get_global_position()); } } break; } @@ -93,13 +92,13 @@ void NavigationObstacle2D::update_agent_shape() { // and add the enclosing shape radius r += cs->get_shape()->get_enclosing_radius(); } - Size2 s = cs->get_global_transform().get_scale(); + Size2 s = cs->get_global_scale(); r *= MAX(s.x, s.y); // Takes the biggest radius radius = MAX(radius, r); } } - Vector2 s = parent_node2d->get_global_transform().get_scale(); + Vector2 s = parent_node2d->get_global_scale(); radius *= MAX(s.x, s.y); if (radius == 0.0) { diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp index ea639ae3a3..72ea6541e3 100644 --- a/scene/2d/navigation_region_2d.cpp +++ b/scene/2d/navigation_region_2d.cpp @@ -30,7 +30,6 @@ #include "navigation_region_2d.h" -#include "core/config/engine.h" #include "core/core_string_names.h" #include "core/math/geometry_2d.h" #include "core/os/mutex.h" @@ -455,7 +454,7 @@ void NavigationRegion2D::_notification(int p_what) { // Draw the region Transform2D xform = get_global_transform(); const NavigationServer2D *ns = NavigationServer2D::get_singleton(); - float radius = ns->map_get_edge_connection_margin(get_world_2d()->get_navigation_map()) / 2.0; + real_t radius = ns->map_get_edge_connection_margin(get_world_2d()->get_navigation_map()) / 2.0; for (int i = 0; i < ns->region_get_connections_count(region); i++) { // Two main points Vector2 a = ns->region_get_connection_pathway_start(region, i); @@ -465,7 +464,7 @@ void NavigationRegion2D::_notification(int p_what) { draw_line(a, b, doors_color); // Draw a circle to illustrate the margins. - float angle = (b - a).angle(); + real_t angle = (b - a).angle(); draw_arc(a, radius, angle + Math_PI / 2.0, angle - Math_PI / 2.0 + Math_TAU, 10, doors_color); draw_arc(b, radius, angle - Math_PI / 2.0, angle + Math_PI / 2.0, 10, doors_color); } diff --git a/scene/2d/node_2d.cpp b/scene/2d/node_2d.cpp index 6fd383ddab..6a8788ee6e 100644 --- a/scene/2d/node_2d.cpp +++ b/scene/2d/node_2d.cpp @@ -30,11 +30,6 @@ #include "node_2d.h" -#include "core/object/message_queue.h" -#include "scene/gui/control.h" -#include "scene/main/window.h" -#include "servers/rendering_server.h" - #ifdef TOOLS_ENABLED Dictionary Node2D::_edit_get_state() const { Dictionary state; @@ -170,10 +165,10 @@ void Node2D::set_scale(const Size2 &p_scale) { } _scale = p_scale; // Avoid having 0 scale values, can lead to errors in physics and rendering. - if (_scale.x == 0) { + if (Math::is_zero_approx(_scale.x)) { _scale.x = CMP_EPSILON; } - if (_scale.y == 0) { + if (Math::is_zero_approx(_scale.y)) { _scale.y = CMP_EPSILON; } _update_transform(); diff --git a/scene/2d/parallax_background.h b/scene/2d/parallax_background.h index 27134dab29..3745c5b587 100644 --- a/scene/2d/parallax_background.h +++ b/scene/2d/parallax_background.h @@ -31,8 +31,6 @@ #ifndef PARALLAX_BACKGROUND_H #define PARALLAX_BACKGROUND_H -#include "scene/2d/camera_2d.h" -#include "scene/2d/node_2d.h" #include "scene/main/canvas_layer.h" class ParallaxBackground : public CanvasLayer { diff --git a/scene/2d/parallax_layer.cpp b/scene/2d/parallax_layer.cpp index 228020d383..67e35cc7a3 100644 --- a/scene/2d/parallax_layer.cpp +++ b/scene/2d/parallax_layer.cpp @@ -30,7 +30,6 @@ #include "parallax_layer.h" -#include "core/config/engine.h" #include "parallax_background.h" void ParallaxLayer::set_motion_scale(const Size2 &p_scale) { @@ -101,6 +100,10 @@ void ParallaxLayer::_notification(int p_what) { _update_mirroring(); } break; case NOTIFICATION_EXIT_TREE: { + if (Engine::get_singleton()->is_editor_hint()) { + break; + } + set_position(orig_offset); set_scale(orig_scale); } break; diff --git a/scene/2d/path_2d.cpp b/scene/2d/path_2d.cpp index 9912612c4f..ed30e871d7 100644 --- a/scene/2d/path_2d.cpp +++ b/scene/2d/path_2d.cpp @@ -30,9 +30,7 @@ #include "path_2d.h" -#include "core/config/engine.h" #include "core/math/geometry_2d.h" -#include "scene/scene_string_names.h" #ifdef TOOLS_ENABLED #include "editor/editor_scale.h" diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp index be619ed60d..30f012c7aa 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -30,17 +30,12 @@ #include "physics_body_2d.h" -#include "core/config/engine.h" #include "core/core_string_names.h" -#include "core/math/math_funcs.h" -#include "core/object/class_db.h" -#include "core/templates/list.h" -#include "core/templates/rid.h" #include "scene/scene_string_names.h" void PhysicsBody2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("move_and_collide", "rel_vec", "infinite_inertia", "exclude_raycast_shapes", "test_only", "safe_margin"), &PhysicsBody2D::_move, DEFVAL(true), DEFVAL(true), DEFVAL(false), DEFVAL(0.08)); - ClassDB::bind_method(D_METHOD("test_move", "from", "rel_vec", "infinite_inertia", "exclude_raycast_shapes", "collision", "safe_margin"), &PhysicsBody2D::test_move, DEFVAL(true), DEFVAL(true), DEFVAL(Variant()), DEFVAL(0.08)); + ClassDB::bind_method(D_METHOD("move_and_collide", "rel_vec", "test_only", "safe_margin"), &PhysicsBody2D::_move, DEFVAL(false), DEFVAL(0.08)); + ClassDB::bind_method(D_METHOD("test_move", "from", "rel_vec", "collision", "safe_margin"), &PhysicsBody2D::test_move, DEFVAL(Variant()), DEFVAL(0.08)); ClassDB::bind_method(D_METHOD("get_collision_exceptions"), &PhysicsBody2D::get_collision_exceptions); ClassDB::bind_method(D_METHOD("add_collision_exception_with", "body"), &PhysicsBody2D::add_collision_exception_with); @@ -59,38 +54,37 @@ PhysicsBody2D::~PhysicsBody2D() { } } -Ref<KinematicCollision2D> PhysicsBody2D::_move(const Vector2 &p_motion, bool p_infinite_inertia, bool p_exclude_raycast_shapes, bool p_test_only, real_t p_margin) { +Ref<KinematicCollision2D> PhysicsBody2D::_move(const Vector2 &p_motion, bool p_test_only, real_t p_margin) { PhysicsServer2D::MotionResult result; - if (move_and_collide(p_motion, p_infinite_inertia, result, p_margin, p_exclude_raycast_shapes, p_test_only)) { + if (move_and_collide(p_motion, result, p_margin, p_test_only)) { if (motion_cache.is_null()) { motion_cache.instantiate(); motion_cache->owner = this; } motion_cache->result = result; - return motion_cache; } return Ref<KinematicCollision2D>(); } -bool PhysicsBody2D::move_and_collide(const Vector2 &p_motion, bool p_infinite_inertia, PhysicsServer2D::MotionResult &r_result, real_t p_margin, bool p_exclude_raycast_shapes, bool p_test_only, bool p_cancel_sliding) { +bool PhysicsBody2D::move_and_collide(const Vector2 &p_motion, PhysicsServer2D::MotionResult &r_result, real_t p_margin, bool p_test_only, bool p_cancel_sliding, bool p_collide_separation_ray, const Set<RID> &p_exclude) { if (is_only_update_transform_changes_enabled()) { ERR_PRINT("Move functions do not work together with 'sync to physics' option. Please read the documentation."); } Transform2D gt = get_global_transform(); - bool colliding = PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_infinite_inertia, p_margin, &r_result, p_exclude_raycast_shapes); + bool colliding = PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_margin, &r_result, p_collide_separation_ray, p_exclude); // Restore direction of motion to be along original motion, // in order to avoid sliding due to recovery, // but only if collision depth is low enough to avoid tunneling. - real_t motion_length = p_motion.length(); - if (motion_length > CMP_EPSILON) { + if (p_cancel_sliding) { + real_t motion_length = p_motion.length(); real_t precision = 0.001; - if (colliding && p_cancel_sliding) { + if (colliding) { // Can't just use margin as a threshold because collision depth is calculated on unsafe motion, // so even in normal resting cases the depth can be a bit more than the margin. precision += motion_length * (r_result.collision_unsafe_fraction - r_result.collision_safe_fraction); @@ -101,30 +95,35 @@ bool PhysicsBody2D::move_and_collide(const Vector2 &p_motion, bool p_infinite_in } if (p_cancel_sliding) { + // When motion is null, recovery is the resulting motion. + Vector2 motion_normal; + if (motion_length > CMP_EPSILON) { + motion_normal = p_motion / motion_length; + } + // Check depth of recovery. - Vector2 motion_normal = p_motion / motion_length; - real_t dot = r_result.motion.dot(motion_normal); - Vector2 recovery = r_result.motion - motion_normal * dot; + real_t projected_length = r_result.travel.dot(motion_normal); + Vector2 recovery = r_result.travel - motion_normal * projected_length; real_t recovery_length = recovery.length(); // Fixes cases where canceling slide causes the motion to go too deep into the ground, - // Becauses we're only taking rest information into account and not general recovery. + // because we're only taking rest information into account and not general recovery. if (recovery_length < (real_t)p_margin + precision) { // Apply adjustment to motion. - r_result.motion = motion_normal * dot; - r_result.remainder = p_motion - r_result.motion; + r_result.travel = motion_normal * projected_length; + r_result.remainder = p_motion - r_result.travel; } } } if (!p_test_only) { - gt.elements[2] += r_result.motion; + gt.elements[2] += r_result.travel; set_global_transform(gt); } return colliding; } -bool PhysicsBody2D::test_move(const Transform2D &p_from, const Vector2 &p_motion, bool p_infinite_inertia, bool p_exclude_raycast_shapes, const Ref<KinematicCollision2D> &r_collision, real_t p_margin) { +bool PhysicsBody2D::test_move(const Transform2D &p_from, const Vector2 &p_motion, const Ref<KinematicCollision2D> &r_collision, real_t p_margin) { ERR_FAIL_COND_V(!is_inside_tree(), false); PhysicsServer2D::MotionResult *r = nullptr; @@ -133,15 +132,14 @@ bool PhysicsBody2D::test_move(const Transform2D &p_from, const Vector2 &p_motion r = const_cast<PhysicsServer2D::MotionResult *>(&r_collision->result); } - return PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), p_from, p_motion, p_infinite_inertia, p_margin, r, p_exclude_raycast_shapes); + return PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), p_from, p_motion, p_margin, r); } TypedArray<PhysicsBody2D> PhysicsBody2D::get_collision_exceptions() { List<RID> exceptions; PhysicsServer2D::get_singleton()->body_get_collision_exceptions(get_rid(), &exceptions); Array ret; - for (List<RID>::Element *E = exceptions.front(); E; E = E->next()) { - RID body = E->get(); + for (const RID &body : exceptions) { ObjectID instance_id = PhysicsServer2D::get_singleton()->body_get_object_instance_id(body); Object *obj = ObjectDB::get_instance(instance_id); PhysicsBody2D *physics_body = Object::cast_to<PhysicsBody2D>(obj); @@ -167,21 +165,13 @@ void PhysicsBody2D::remove_collision_exception_with(Node *p_node) { void StaticBody2D::set_constant_linear_velocity(const Vector2 &p_vel) { constant_linear_velocity = p_vel; - if (kinematic_motion) { - _update_kinematic_motion(); - } else { - PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, constant_linear_velocity); - } + PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, constant_linear_velocity); } void StaticBody2D::set_constant_angular_velocity(real_t p_vel) { constant_angular_velocity = p_vel; - if (kinematic_motion) { - _update_kinematic_motion(); - } else { - PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, constant_angular_velocity); - } + PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, constant_angular_velocity); } Vector2 StaticBody2D::get_constant_linear_velocity() const { @@ -211,74 +201,22 @@ Ref<PhysicsMaterial> StaticBody2D::get_physics_material_override() const { return physics_material_override; } -void StaticBody2D::set_kinematic_motion_enabled(bool p_enabled) { - if (p_enabled == kinematic_motion) { - return; - } - - kinematic_motion = p_enabled; - - if (kinematic_motion) { - set_body_mode(PhysicsServer2D::BODY_MODE_KINEMATIC); - } else { - set_body_mode(PhysicsServer2D::BODY_MODE_STATIC); - } - - _update_kinematic_motion(); -} - -bool StaticBody2D::is_kinematic_motion_enabled() const { - return kinematic_motion; -} - -void StaticBody2D::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - return; - } -#endif - - ERR_FAIL_COND(!kinematic_motion); - - real_t delta_time = get_physics_process_delta_time(); - - Transform2D new_transform = get_global_transform(); - - new_transform.translate(constant_linear_velocity * delta_time); - new_transform.set_rotation(new_transform.get_rotation() + constant_angular_velocity * delta_time); - - PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_TRANSFORM, new_transform); - - // Propagate transform change to node. - set_block_transform_notify(true); - set_global_transform(new_transform); - set_block_transform_notify(false); - } break; - } -} - void StaticBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_constant_linear_velocity", "vel"), &StaticBody2D::set_constant_linear_velocity); ClassDB::bind_method(D_METHOD("set_constant_angular_velocity", "vel"), &StaticBody2D::set_constant_angular_velocity); ClassDB::bind_method(D_METHOD("get_constant_linear_velocity"), &StaticBody2D::get_constant_linear_velocity); ClassDB::bind_method(D_METHOD("get_constant_angular_velocity"), &StaticBody2D::get_constant_angular_velocity); - ClassDB::bind_method(D_METHOD("set_kinematic_motion_enabled", "enabled"), &StaticBody2D::set_kinematic_motion_enabled); - ClassDB::bind_method(D_METHOD("is_kinematic_motion_enabled"), &StaticBody2D::is_kinematic_motion_enabled); - ClassDB::bind_method(D_METHOD("set_physics_material_override", "physics_material_override"), &StaticBody2D::set_physics_material_override); 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::BOOL, "kinematic_motion"), "set_kinematic_motion_enabled", "is_kinematic_motion_enabled"); } -StaticBody2D::StaticBody2D() : - PhysicsBody2D(PhysicsServer2D::BODY_MODE_STATIC) { +StaticBody2D::StaticBody2D(PhysicsServer2D::BodyMode p_mode) : + PhysicsBody2D(p_mode) { } void StaticBody2D::_reload_physics_characteristics() { @@ -291,21 +229,84 @@ void StaticBody2D::_reload_physics_characteristics() { } } -void StaticBody2D::_update_kinematic_motion() { +void AnimatableBody2D::set_sync_to_physics(bool p_enable) { + if (sync_to_physics == p_enable) { + return; + } + + sync_to_physics = p_enable; + + _update_kinematic_motion(); +} + +bool AnimatableBody2D::is_sync_to_physics_enabled() const { + return sync_to_physics; +} + +void AnimatableBody2D::_update_kinematic_motion() { #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { return; } #endif - if (kinematic_motion) { - if (!Math::is_zero_approx(constant_angular_velocity) || !constant_linear_velocity.is_equal_approx(Vector2())) { - set_physics_process_internal(true); - return; - } + if (sync_to_physics) { + PhysicsServer2D::get_singleton()->body_set_state_sync_callback(get_rid(), this, _body_state_changed_callback); + set_only_update_transform_changes(true); + set_notify_local_transform(true); + } else { + PhysicsServer2D::get_singleton()->body_set_state_sync_callback(get_rid(), nullptr, nullptr); + set_only_update_transform_changes(false); + set_notify_local_transform(false); } +} + +void AnimatableBody2D::_body_state_changed_callback(void *p_instance, PhysicsDirectBodyState2D *p_state) { + AnimatableBody2D *body = (AnimatableBody2D *)p_instance; + body->_body_state_changed(p_state); +} - set_physics_process_internal(false); +void AnimatableBody2D::_body_state_changed(PhysicsDirectBodyState2D *p_state) { + if (!sync_to_physics) { + return; + } + + last_valid_transform = p_state->get_transform(); + set_notify_local_transform(false); + set_global_transform(last_valid_transform); + set_notify_local_transform(true); +} + +void AnimatableBody2D::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + last_valid_transform = get_global_transform(); + } break; + + case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { + // Used by sync to physics, send the new transform to the physics... + Transform2D new_transform = get_global_transform(); + + PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_TRANSFORM, new_transform); + + // ... but then revert changes. + set_notify_local_transform(false); + set_global_transform(last_valid_transform); + set_notify_local_transform(true); + } break; + } +} + +void AnimatableBody2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_sync_to_physics", "enable"), &AnimatableBody2D::set_sync_to_physics); + ClassDB::bind_method(D_METHOD("is_sync_to_physics_enabled"), &AnimatableBody2D::is_sync_to_physics_enabled); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync_to_physics"), "set_sync_to_physics", "is_sync_to_physics_enabled"); +} + +AnimatableBody2D::AnimatableBody2D() : + StaticBody2D(PhysicsServer2D::BODY_MODE_KINEMATIC) { + _update_kinematic_motion(); } void RigidBody2D::_body_enter_tree(ObjectID p_id) { @@ -421,27 +422,27 @@ struct _RigidBody2DInOut { int local_shape = 0; }; -void RigidBody2D::_direct_state_changed(Object *p_state) { -#ifdef DEBUG_ENABLED - state = Object::cast_to<PhysicsDirectBodyState2D>(p_state); - ERR_FAIL_NULL_MSG(state, "Method '_direct_state_changed' must receive a valid PhysicsDirectBodyState2D object as argument"); -#else - state = (PhysicsDirectBodyState2D *)p_state; //trust it -#endif +void RigidBody2D::_body_state_changed_callback(void *p_instance, PhysicsDirectBodyState2D *p_state) { + RigidBody2D *body = (RigidBody2D *)p_instance; + body->_body_state_changed(p_state); +} +void RigidBody2D::_body_state_changed(PhysicsDirectBodyState2D *p_state) { set_block_transform_notify(true); // don't want notify (would feedback loop) if (mode != MODE_KINEMATIC) { - set_global_transform(state->get_transform()); + set_global_transform(p_state->get_transform()); } - linear_velocity = state->get_linear_velocity(); - angular_velocity = state->get_angular_velocity(); - if (sleeping != state->is_sleeping()) { - sleeping = state->is_sleeping(); + + linear_velocity = p_state->get_linear_velocity(); + angular_velocity = p_state->get_angular_velocity(); + + if (sleeping != p_state->is_sleeping()) { + sleeping = p_state->is_sleeping(); emit_signal(SceneStringNames::get_singleton()->sleeping_state_changed); } - if (get_script_instance()) { - get_script_instance()->call("_integrate_forces", state); - } + + GDVIRTUAL_CALL(_integrate_forces, p_state); + set_block_transform_notify(false); // want it back if (contact_monitor) { @@ -456,20 +457,18 @@ void RigidBody2D::_direct_state_changed(Object *p_state) { } } - _RigidBody2DInOut *toadd = (_RigidBody2DInOut *)alloca(state->get_contact_count() * sizeof(_RigidBody2DInOut)); + _RigidBody2DInOut *toadd = (_RigidBody2DInOut *)alloca(p_state->get_contact_count() * sizeof(_RigidBody2DInOut)); int toadd_count = 0; //state->get_contact_count(); RigidBody2D_RemoveAction *toremove = (RigidBody2D_RemoveAction *)alloca(rc * sizeof(RigidBody2D_RemoveAction)); int toremove_count = 0; //put the ones to add - for (int i = 0; i < state->get_contact_count(); i++) { - RID rid = state->get_contact_collider(i); - ObjectID obj = state->get_contact_collider_id(i); - int local_shape = state->get_contact_local_shape(i); - int shape = state->get_contact_collider_shape(i); - - //bool found=false; + for (int i = 0; i < p_state->get_contact_count(); i++) { + RID rid = p_state->get_contact_collider(i); + ObjectID obj = p_state->get_contact_collider_id(i); + 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); if (!E) { @@ -522,8 +521,6 @@ void RigidBody2D::_direct_state_changed(Object *p_state) { contact_monitor->locked = false; } - - state = nullptr; } void RigidBody2D::set_mode(Mode p_mode) { @@ -563,11 +560,53 @@ real_t RigidBody2D::get_mass() const { void RigidBody2D::set_inertia(real_t p_inertia) { ERR_FAIL_COND(p_inertia < 0); - PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_INERTIA, p_inertia); + inertia = p_inertia; + PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_INERTIA, inertia); } real_t RigidBody2D::get_inertia() const { - return PhysicsServer2D::get_singleton()->body_get_param(get_rid(), PhysicsServer2D::BODY_PARAM_INERTIA); + return inertia; +} + +void RigidBody2D::set_center_of_mass_mode(CenterOfMassMode p_mode) { + if (center_of_mass_mode == p_mode) { + return; + } + + center_of_mass_mode = p_mode; + + switch (center_of_mass_mode) { + case CENTER_OF_MASS_MODE_AUTO: { + center_of_mass = Vector2(); + PhysicsServer2D::get_singleton()->body_reset_mass_properties(get_rid()); + if (inertia != 0.0) { + PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_INERTIA, inertia); + } + } break; + + case CENTER_OF_MASS_MODE_CUSTOM: { + PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_CENTER_OF_MASS, center_of_mass); + } break; + } +} + +RigidBody2D::CenterOfMassMode RigidBody2D::get_center_of_mass_mode() const { + return center_of_mass_mode; +} + +void RigidBody2D::set_center_of_mass(const Vector2 &p_center_of_mass) { + if (center_of_mass == p_center_of_mass) { + return; + } + + ERR_FAIL_COND(center_of_mass_mode != CENTER_OF_MASS_MODE_CUSTOM); + center_of_mass = p_center_of_mass; + + PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_CENTER_OF_MASS, center_of_mass); +} + +const Vector2 &RigidBody2D::get_center_of_mass() const { + return center_of_mass; } void RigidBody2D::set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override) { @@ -619,25 +658,15 @@ real_t RigidBody2D::get_angular_damp() const { } void RigidBody2D::set_axis_velocity(const Vector2 &p_axis) { - Vector2 v = state ? state->get_linear_velocity() : linear_velocity; Vector2 axis = p_axis.normalized(); - v -= axis * axis.dot(v); - v += p_axis; - if (state) { - set_linear_velocity(v); - } else { - PhysicsServer2D::get_singleton()->body_set_axis_velocity(get_rid(), p_axis); - linear_velocity = v; - } + linear_velocity -= axis * axis.dot(linear_velocity); + linear_velocity += p_axis; + PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, linear_velocity); } void RigidBody2D::set_linear_velocity(const Vector2 &p_velocity) { linear_velocity = p_velocity; - if (state) { - state->set_linear_velocity(linear_velocity); - } else { - PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, linear_velocity); - } + PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, linear_velocity); } Vector2 RigidBody2D::get_linear_velocity() const { @@ -646,11 +675,7 @@ Vector2 RigidBody2D::get_linear_velocity() const { void RigidBody2D::set_angular_velocity(real_t p_velocity) { angular_velocity = p_velocity; - if (state) { - state->set_angular_velocity(angular_velocity); - } else { - PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, angular_velocity); - } + PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, angular_velocity); } real_t RigidBody2D::get_angular_velocity() const { @@ -835,6 +860,12 @@ void RigidBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_inertia"), &RigidBody2D::get_inertia); ClassDB::bind_method(D_METHOD("set_inertia", "inertia"), &RigidBody2D::set_inertia); + ClassDB::bind_method(D_METHOD("set_center_of_mass_mode", "mode"), &RigidBody2D::set_center_of_mass_mode); + ClassDB::bind_method(D_METHOD("get_center_of_mass_mode"), &RigidBody2D::get_center_of_mass_mode); + + ClassDB::bind_method(D_METHOD("set_center_of_mass", "center_of_mass"), &RigidBody2D::set_center_of_mass); + ClassDB::bind_method(D_METHOD("get_center_of_mass"), &RigidBody2D::get_center_of_mass); + ClassDB::bind_method(D_METHOD("set_physics_material_override", "physics_material_override"), &RigidBody2D::set_physics_material_override); ClassDB::bind_method(D_METHOD("get_physics_material_override"), &RigidBody2D::get_physics_material_override); @@ -888,11 +919,14 @@ void RigidBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_colliding_bodies"), &RigidBody2D::get_colliding_bodies); - BIND_VMETHOD(MethodInfo("_integrate_forces", PropertyInfo(Variant::OBJECT, "state", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsDirectBodyState2D"))); + GDVIRTUAL_BIND(_integrate_forces, "state"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Dynamic,Static,DynamicLocked,Kinematic"), "set_mode", "get_mode"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.01,65535,0.01,exp"), "set_mass", "get_mass"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "inertia", PROPERTY_HINT_RANGE, "0.01,65535,0.01,exp", PROPERTY_USAGE_NONE), "set_inertia", "get_inertia"); + 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::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_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"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "custom_integrator"), "set_use_custom_integrator", "is_using_custom_integrator"); @@ -922,14 +956,25 @@ void RigidBody2D::_bind_methods() { BIND_ENUM_CONSTANT(MODE_DYNAMIC_LOCKED); BIND_ENUM_CONSTANT(MODE_KINEMATIC); + BIND_ENUM_CONSTANT(CENTER_OF_MASS_MODE_AUTO); + BIND_ENUM_CONSTANT(CENTER_OF_MASS_MODE_CUSTOM); + BIND_ENUM_CONSTANT(CCD_MODE_DISABLED); BIND_ENUM_CONSTANT(CCD_MODE_CAST_RAY); BIND_ENUM_CONSTANT(CCD_MODE_CAST_SHAPE); } +void RigidBody2D::_validate_property(PropertyInfo &property) const { + if (center_of_mass_mode != CENTER_OF_MASS_MODE_CUSTOM) { + if (property.name == "center_of_mass") { + property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL; + } + } +} + RigidBody2D::RigidBody2D() : PhysicsBody2D(PhysicsServer2D::BODY_MODE_DYNAMIC) { - PhysicsServer2D::get_singleton()->body_set_force_integration_callback(get_rid(), callable_mp(this, &RigidBody2D::_direct_state_changed)); + PhysicsServer2D::get_singleton()->body_set_state_sync_callback(get_rid(), this, _body_state_changed_callback); } RigidBody2D::~RigidBody2D() { @@ -950,169 +995,321 @@ void RigidBody2D::_reload_physics_characteristics() { ////////////////////////// -//so, if you pass 45 as limit, avoid numerical precision errors when angle is 45. +// So, if you pass 45 as limit, avoid numerical precision errors when angle is 45. #define FLOOR_ANGLE_THRESHOLD 0.01 -void CharacterBody2D::move_and_slide() { - Vector2 body_velocity_normal = linear_velocity.normalized(); +bool CharacterBody2D::move_and_slide() { + // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky. + double delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time(); - bool was_on_floor = on_floor; + Vector2 current_platform_velocity = platform_velocity; - Vector2 current_floor_velocity = floor_velocity; - if (on_floor && on_floor_body.is_valid()) { - //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(on_floor_body); - if (bs) { - current_floor_velocity = bs->get_linear_velocity(); + if ((on_floor || on_wall) && platform_rid.is_valid()) { + bool excluded = false; + if (on_floor) { + excluded = (moving_platform_floor_layers & platform_layer) == 0; + } else if (on_wall) { + excluded = (moving_platform_wall_layers & platform_layer) == 0; + } + if (!excluded) { + //this approach makes sure there is less delay between the actual body velocity and the one we saved + PhysicsDirectBodyState2D *bs = PhysicsServer2D::get_singleton()->body_get_direct_state(platform_rid); + if (bs) { + Transform2D gt = get_global_transform(); + Vector2 local_position = gt.elements[2] - bs->get_transform().elements[2]; + current_platform_velocity = bs->get_velocity_at_local_position(local_position); + } + } else { + current_platform_velocity = Vector2(); } } - // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky - Vector2 motion = (current_floor_velocity + linear_velocity) * (Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time()); + motion_results.clear(); + bool was_on_floor = on_floor; on_floor = false; - on_floor_body = RID(); on_ceiling = false; on_wall = false; - motion_results.clear(); + + if (!current_platform_velocity.is_equal_approx(Vector2())) { + PhysicsServer2D::MotionResult floor_result; + Set<RID> exclude; + exclude.insert(platform_rid); + if (move_and_collide(current_platform_velocity * delta, floor_result, margin, false, false, false, exclude)) { + motion_results.push_back(floor_result); + _set_collision_direction(floor_result); + } + } + + if (motion_mode == MOTION_MODE_GROUNDED) { + _move_and_slide_grounded(delta, was_on_floor, current_platform_velocity); + } else { + _move_and_slide_free(delta); + } + + if (!on_floor && !on_wall) { + // Add last platform velocity when just left a moving platform. + linear_velocity += current_platform_velocity; + } + + return motion_results.size() > 0; +} + +void CharacterBody2D::_move_and_slide_grounded(double p_delta, bool p_was_on_floor, const Vector2 &p_prev_platform_velocity) { + Vector2 motion = linear_velocity * p_delta; + Vector2 motion_slide_up = motion.slide(up_direction); + + Vector2 prev_floor_normal = floor_normal; + RID prev_platform_rid = platform_rid; + int prev_platform_layer = platform_layer; + + platform_rid = RID(); floor_normal = Vector2(); - floor_velocity = Vector2(); + platform_velocity = Vector2(); + + // No sliding on first attempt to keep floor motion stable when possible, + // When stop on slope is enabled or when there is no up direction. + bool sliding_enabled = !floor_stop_on_slope; + // Constant speed can be applied only the first time sliding is enabled. + bool can_apply_constant_speed = sliding_enabled; + bool first_slide = true; + bool vel_dir_facing_up = linear_velocity.dot(up_direction) > 0; + Vector2 last_travel; - // No sliding on first attempt to keep floor motion stable when possible. - bool sliding_enabled = false; for (int iteration = 0; iteration < max_slides; ++iteration) { PhysicsServer2D::MotionResult result; - bool found_collision = false; - - for (int i = 0; i < 2; ++i) { - bool collided; - if (i == 0) { //collide - collided = move_and_collide(motion, infinite_inertia, result, margin, true, false, !sliding_enabled); - if (!collided) { - motion = Vector2(); //clear because no collision happened and motion completed - } - } else { //separate raycasts (if any) - collided = separate_raycast_shapes(result); - if (collided) { - result.remainder = motion; //keep - result.motion = Vector2(); - } - } - if (collided) { - found_collision = true; + Vector2 prev_position = get_global_transform().elements[2]; - motion_results.push_back(result); + bool collided = move_and_collide(motion, result, margin, false, !sliding_enabled); - if (up_direction == Vector2()) { - //all is a wall - on_wall = true; + if (collided) { + motion_results.push_back(result); + _set_collision_direction(result); + + if (on_floor && floor_stop_on_slope && (linear_velocity.normalized() + up_direction).length() < 0.01) { + Transform2D gt = get_global_transform(); + if (result.travel.length() > margin) { + gt.elements[2] -= result.travel.slide(up_direction); } else { - if (Math::acos(result.collision_normal.dot(up_direction)) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor - - on_floor = true; - floor_normal = result.collision_normal; - on_floor_body = result.collider; - floor_velocity = result.collider_velocity; - - if (stop_on_slope) { - if ((body_velocity_normal + up_direction).length() < 0.01) { - Transform2D gt = get_global_transform(); - gt.elements[2] -= result.motion.slide(up_direction); - set_global_transform(gt); - linear_velocity = Vector2(); - return; - } - } - } else if (Math::acos(result.collision_normal.dot(-up_direction)) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling - on_ceiling = true; - } else { - on_wall = true; - } + gt.elements[2] -= result.travel; } + set_global_transform(gt); + linear_velocity = Vector2(); + motion = Vector2(); + break; + } - if (sliding_enabled || !on_floor) { - motion = result.remainder.slide(result.collision_normal); - linear_velocity = linear_velocity.slide(result.collision_normal); + if (result.remainder.is_equal_approx(Vector2())) { + motion = Vector2(); + break; + } + + // Move on floor only checks. + if (floor_block_on_wall && on_wall && motion_slide_up.dot(result.collision_normal) <= 0) { + // Avoid to move forward on a wall if floor_block_on_wall is true. + if (p_was_on_floor && !on_floor && !vel_dir_facing_up) { + // If the movement is large the body can be prevented from reaching the walls. + if (result.travel.length() <= margin) { + // Cancels the motion. + Transform2D gt = get_global_transform(); + gt.elements[2] -= result.travel; + set_global_transform(gt); + } + on_floor = true; + platform_rid = prev_platform_rid; + platform_layer = prev_platform_layer; + platform_velocity = p_prev_platform_velocity; + floor_normal = prev_floor_normal; + linear_velocity = Vector2(); + motion = Vector2(); + break; + } + // Prevents the body from being able to climb a slope when it moves forward against the wall. + else if (!on_floor) { + motion = up_direction * up_direction.dot(result.remainder); + motion = motion.slide(result.collision_normal); } else { motion = result.remainder; } } + // Constant Speed when the slope is upward. + else if (floor_constant_speed && is_on_floor_only() && can_apply_constant_speed && p_was_on_floor && motion.dot(result.collision_normal) < 0) { + can_apply_constant_speed = false; + Vector2 motion_slide_norm = result.remainder.slide(result.collision_normal).normalized(); + motion = motion_slide_norm * (motion_slide_up.length() - result.travel.slide(up_direction).length() - last_travel.slide(up_direction).length()); + } + // Regular sliding, the last part of the test handle the case when you don't want to slide on the ceiling. + else if ((sliding_enabled || !on_floor) && (!on_ceiling || slide_on_ceiling || !vel_dir_facing_up)) { + Vector2 slide_motion = result.remainder.slide(result.collision_normal); + if (slide_motion.dot(linear_velocity) > 0.0) { + motion = slide_motion; + } else { + motion = Vector2(); + } + if (slide_on_ceiling && on_ceiling) { + // Apply slide only in the direction of the input motion, otherwise just stop to avoid jittering when moving against a wall. + if (vel_dir_facing_up) { + linear_velocity = linear_velocity.slide(result.collision_normal); + } else { + // Avoid acceleration in slope when falling. + linear_velocity = up_direction * up_direction.dot(linear_velocity); + } + } + } + // No sliding on first attempt to keep floor motion stable when possible. + else { + motion = result.remainder; + if (on_ceiling && !slide_on_ceiling && vel_dir_facing_up) { + linear_velocity = linear_velocity.slide(up_direction); + motion = motion.slide(up_direction); + } + } + last_travel = result.travel; + } + // When you move forward in a downward slope you don’t collide because you will be in the air. + // This test ensures that constant speed is applied, only if the player is still on the ground after the snap is applied. + else if (floor_constant_speed && first_slide && _on_floor_if_snapped(p_was_on_floor, vel_dir_facing_up)) { + can_apply_constant_speed = false; sliding_enabled = true; + Transform2D gt = get_global_transform(); + gt.elements[2] = prev_position; + set_global_transform(gt); + + Vector2 motion_slide_norm = motion.slide(prev_floor_normal).normalized(); + motion = motion_slide_norm * (motion_slide_up.length()); + collided = true; } - if (!found_collision || motion == Vector2()) { + can_apply_constant_speed = !can_apply_constant_speed && !sliding_enabled; + sliding_enabled = true; + first_slide = false; + + if (!collided || motion.is_equal_approx(Vector2())) { break; } } - if (!was_on_floor || snap == Vector2()) { + _snap_on_floor(p_was_on_floor, vel_dir_facing_up); + + // Reset the gravity accumulation when touching the ground. + if (on_floor && !vel_dir_facing_up) { + linear_velocity = linear_velocity.slide(up_direction); + } +} + +void CharacterBody2D::_move_and_slide_free(double p_delta) { + Vector2 motion = linear_velocity * p_delta; + + platform_rid = RID(); + floor_normal = Vector2(); + platform_velocity = Vector2(); + + bool first_slide = true; + for (int iteration = 0; iteration < max_slides; ++iteration) { + PhysicsServer2D::MotionResult result; + + bool collided = move_and_collide(motion, result, margin, false, false); + + if (collided) { + motion_results.push_back(result); + _set_collision_direction(result); + + if (free_mode_min_slide_angle != 0 && result.get_angle(-linear_velocity.normalized()) < free_mode_min_slide_angle + FLOOR_ANGLE_THRESHOLD) { + motion = Vector2(); + } else if (first_slide) { + Vector2 motion_slide_norm = result.remainder.slide(result.collision_normal).normalized(); + motion = motion_slide_norm * (motion.length() - result.travel.length()); + } else { + motion = result.remainder.slide(result.collision_normal); + } + + if (motion.dot(linear_velocity) <= 0.0) { + motion = Vector2(); + } + } + + first_slide = false; + + if (!collided || motion.is_equal_approx(Vector2())) { + break; + } + } +} + +void CharacterBody2D::_snap_on_floor(bool was_on_floor, bool vel_dir_facing_up) { + if (Math::is_equal_approx(floor_snap_length, 0) || on_floor || !was_on_floor || vel_dir_facing_up) { return; } - // Apply snap. Transform2D gt = get_global_transform(); PhysicsServer2D::MotionResult result; - if (move_and_collide(snap, infinite_inertia, result, margin, false, true)) { + if (move_and_collide(up_direction * -floor_snap_length, result, margin, true, false, true)) { bool apply = true; - if (up_direction != Vector2()) { - if (Math::acos(result.collision_normal.dot(up_direction)) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { - on_floor = true; - floor_normal = result.collision_normal; - on_floor_body = result.collider; - floor_velocity = result.collider_velocity; - if (stop_on_slope) { - // move and collide may stray the object a bit because of pre un-stucking, - // so only ensure that motion happens on floor direction in this case. - result.motion = up_direction * up_direction.dot(result.motion); + if (result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { + on_floor = true; + floor_normal = result.collision_normal; + _set_platform_data(result); + + if (floor_stop_on_slope) { + // move and collide may stray the object a bit because of pre un-stucking, + // so only ensure that motion happens on floor direction in this case. + if (result.travel.length() > margin) { + result.travel = up_direction * up_direction.dot(result.travel); + } else { + result.travel = Vector2(); } - - } else { - apply = false; } + } else { + apply = false; } if (apply) { - gt.elements[2] += result.motion; + gt.elements[2] += result.travel; set_global_transform(gt); } } } -bool CharacterBody2D::separate_raycast_shapes(PhysicsServer2D::MotionResult &r_result) { - PhysicsServer2D::SeparationResult sep_res[8]; //max 8 rays - - Transform2D gt = get_global_transform(); +bool CharacterBody2D::_on_floor_if_snapped(bool was_on_floor, bool vel_dir_facing_up) { + if (Math::is_equal_approx(floor_snap_length, 0) || up_direction == Vector2() || on_floor || !was_on_floor || vel_dir_facing_up) { + return false; + } - Vector2 recover; - int hits = PhysicsServer2D::get_singleton()->body_test_ray_separation(get_rid(), gt, infinite_inertia, recover, sep_res, 8, margin); - int deepest = -1; - real_t deepest_depth; - for (int i = 0; i < hits; i++) { - if (deepest == -1 || sep_res[i].collision_depth > deepest_depth) { - deepest = i; - deepest_depth = sep_res[i].collision_depth; + PhysicsServer2D::MotionResult result; + if (move_and_collide(up_direction * -floor_snap_length, result, margin, true, false, true)) { + if (result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { + return true; } } - gt.elements[2] += recover; - set_global_transform(gt); - - if (deepest != -1) { - r_result.collider_id = sep_res[deepest].collider_id; - r_result.collider_metadata = sep_res[deepest].collider_metadata; - r_result.collider_shape = sep_res[deepest].collider_shape; - r_result.collider_velocity = sep_res[deepest].collider_velocity; - r_result.collision_point = sep_res[deepest].collision_point; - r_result.collision_normal = sep_res[deepest].collision_normal; - r_result.collision_local_shape = sep_res[deepest].collision_local_shape; - r_result.motion = recover; - r_result.remainder = Vector2(); - - return true; + return false; +} + +void CharacterBody2D::_set_collision_direction(const PhysicsServer2D::MotionResult &p_result) { + if (motion_mode == MOTION_MODE_GROUNDED && p_result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor + on_floor = true; + floor_normal = p_result.collision_normal; + _set_platform_data(p_result); + } else if (motion_mode == MOTION_MODE_GROUNDED && p_result.get_angle(-up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling + on_ceiling = true; } else { - return false; + on_wall = true; + // Don't apply wall velocity when the collider is a CharacterBody2D. + if (Object::cast_to<CharacterBody2D>(ObjectDB::get_instance(p_result.collider_id)) == nullptr) { + _set_platform_data(p_result); + } + } +} + +void CharacterBody2D::_set_platform_data(const PhysicsServer2D::MotionResult &p_result) { + platform_rid = p_result.collider; + platform_velocity = p_result.collider_velocity; + platform_layer = 0; + CollisionObject2D *collision_object = Object::cast_to<CollisionObject2D>(ObjectDB::get_instance(p_result.collider_id)); + if (collision_object) { + platform_layer = collision_object->get_collision_layer(); } } @@ -1128,23 +1325,40 @@ bool CharacterBody2D::is_on_floor() const { return on_floor; } +bool CharacterBody2D::is_on_floor_only() const { + return on_floor && !on_wall && !on_ceiling; +} + bool CharacterBody2D::is_on_wall() const { return on_wall; } +bool CharacterBody2D::is_on_wall_only() const { + return on_wall && !on_floor && !on_ceiling; +} + bool CharacterBody2D::is_on_ceiling() const { return on_ceiling; } +bool CharacterBody2D::is_on_ceiling_only() const { + return on_ceiling && !on_floor && !on_wall; +} + Vector2 CharacterBody2D::get_floor_normal() const { return floor_normal; } -Vector2 CharacterBody2D::get_floor_velocity() const { - return floor_velocity; +real_t CharacterBody2D::get_floor_angle(const Vector2 &p_up_direction) const { + ERR_FAIL_COND_V(p_up_direction == Vector2(), 0); + return Math::acos(floor_normal.dot(p_up_direction)); } -int CharacterBody2D::get_slide_count() const { +Vector2 CharacterBody2D::get_platform_velocity() const { + return platform_velocity; +} + +int CharacterBody2D::get_slide_collision_count() const { return motion_results.size(); } @@ -1168,66 +1382,75 @@ Ref<KinematicCollision2D> CharacterBody2D::_get_slide_collision(int p_bounce) { return slide_colliders[p_bounce]; } -void CharacterBody2D::set_sync_to_physics(bool p_enable) { - if (sync_to_physics == p_enable) { - return; +Ref<KinematicCollision2D> CharacterBody2D::_get_last_slide_collision() { + if (motion_results.size() == 0) { + return Ref<KinematicCollision2D>(); } - sync_to_physics = p_enable; + return _get_slide_collision(motion_results.size() - 1); +} - if (Engine::get_singleton()->is_editor_hint()) { - return; - } +void CharacterBody2D::set_safe_margin(real_t p_margin) { + margin = p_margin; +} - if (p_enable) { - PhysicsServer2D::get_singleton()->body_set_force_integration_callback(get_rid(), callable_mp(this, &CharacterBody2D::_direct_state_changed)); - set_only_update_transform_changes(true); - set_notify_local_transform(true); - } else { - PhysicsServer2D::get_singleton()->body_set_force_integration_callback(get_rid(), Callable()); - set_only_update_transform_changes(false); - set_notify_local_transform(false); - } +real_t CharacterBody2D::get_safe_margin() const { + return margin; } -bool CharacterBody2D::is_sync_to_physics_enabled() const { - return sync_to_physics; +bool CharacterBody2D::is_floor_stop_on_slope_enabled() const { + return floor_stop_on_slope; } -void CharacterBody2D::_direct_state_changed(Object *p_state) { - if (!sync_to_physics) { - return; - } +void CharacterBody2D::set_floor_stop_on_slope_enabled(bool p_enabled) { + floor_stop_on_slope = p_enabled; +} - PhysicsDirectBodyState2D *state = Object::cast_to<PhysicsDirectBodyState2D>(p_state); - ERR_FAIL_NULL_MSG(state, "Method '_direct_state_changed' must receive a valid PhysicsDirectBodyState2D object as argument"); +bool CharacterBody2D::is_floor_constant_speed_enabled() const { + return floor_constant_speed; +} - last_valid_transform = state->get_transform(); - set_notify_local_transform(false); - set_global_transform(last_valid_transform); - set_notify_local_transform(true); +void CharacterBody2D::set_floor_constant_speed_enabled(bool p_enabled) { + floor_constant_speed = p_enabled; } -void CharacterBody2D::set_safe_margin(real_t p_margin) { - margin = p_margin; +bool CharacterBody2D::is_floor_block_on_wall_enabled() const { + return floor_block_on_wall; } -real_t CharacterBody2D::get_safe_margin() const { - return margin; +void CharacterBody2D::set_floor_block_on_wall_enabled(bool p_enabled) { + floor_block_on_wall = p_enabled; +} + +bool CharacterBody2D::is_slide_on_ceiling_enabled() const { + return slide_on_ceiling; +} + +void CharacterBody2D::set_slide_on_ceiling_enabled(bool p_enabled) { + slide_on_ceiling = p_enabled; +} + +uint32_t CharacterBody2D::get_moving_platform_floor_layers() const { + return moving_platform_floor_layers; +} + +void CharacterBody2D::set_moving_platform_floor_layers(uint32_t p_exclude_layers) { + moving_platform_floor_layers = p_exclude_layers; } -bool CharacterBody2D::is_stop_on_slope_enabled() const { - return stop_on_slope; +uint32_t CharacterBody2D::get_moving_platform_wall_layers() const { + return moving_platform_wall_layers; } -void CharacterBody2D::set_stop_on_slope_enabled(bool p_enabled) { - stop_on_slope = p_enabled; +void CharacterBody2D::set_moving_platform_wall_layers(uint32_t p_exclude_layers) { + moving_platform_wall_layers = p_exclude_layers; } -bool CharacterBody2D::is_infinite_inertia_enabled() const { - return infinite_inertia; +void CharacterBody2D::set_motion_mode(MotionMode p_mode) { + motion_mode = p_mode; } -void CharacterBody2D::set_infinite_inertia_enabled(bool p_enabled) { - infinite_inertia = p_enabled; + +CharacterBody2D::MotionMode CharacterBody2D::get_motion_mode() const { + return motion_mode; } int CharacterBody2D::get_max_slides() const { @@ -1235,7 +1458,7 @@ int CharacterBody2D::get_max_slides() const { } void CharacterBody2D::set_max_slides(int p_max_slides) { - ERR_FAIL_COND(p_max_slides > 0); + ERR_FAIL_COND(p_max_slides < 1); max_slides = p_max_slides; } @@ -1247,12 +1470,21 @@ void CharacterBody2D::set_floor_max_angle(real_t p_radians) { floor_max_angle = p_radians; } -const Vector2 &CharacterBody2D::get_snap() const { - return snap; +real_t CharacterBody2D::get_floor_snap_length() { + return floor_snap_length; +} + +void CharacterBody2D::set_floor_snap_length(real_t p_floor_snap_length) { + ERR_FAIL_COND(p_floor_snap_length < 0); + floor_snap_length = p_floor_snap_length; +} + +real_t CharacterBody2D::get_free_mode_min_slide_angle() const { + return free_mode_min_slide_angle; } -void CharacterBody2D::set_snap(const Vector2 &p_snap) { - snap = p_snap; +void CharacterBody2D::set_free_mode_min_slide_angle(real_t p_radians) { + free_mode_min_slide_angle = p_radians; } const Vector2 &CharacterBody2D::get_up_direction() const { @@ -1260,31 +1492,20 @@ const Vector2 &CharacterBody2D::get_up_direction() const { } void CharacterBody2D::set_up_direction(const Vector2 &p_up_direction) { + ERR_FAIL_COND_MSG(p_up_direction == Vector2(), "up_direction can't be equal to Vector2.ZERO, consider using Free motion mode instead."); up_direction = p_up_direction.normalized(); } void CharacterBody2D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { - last_valid_transform = get_global_transform(); - // Reset move_and_slide() data. on_floor = false; - on_floor_body = RID(); + platform_rid = RID(); on_ceiling = false; on_wall = false; motion_results.clear(); - floor_velocity = Vector2(); - } break; - - case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { - // Used by sync to physics, send the new transform to the physics. - Transform2D new_transform = get_global_transform(); - PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_TRANSFORM, new_transform); - // But then revert changes. - set_notify_local_transform(false); - set_global_transform(last_valid_transform); - set_notify_local_transform(true); + platform_velocity = Vector2(); } break; } } @@ -1297,40 +1518,78 @@ void CharacterBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_safe_margin", "pixels"), &CharacterBody2D::set_safe_margin); ClassDB::bind_method(D_METHOD("get_safe_margin"), &CharacterBody2D::get_safe_margin); - ClassDB::bind_method(D_METHOD("is_stop_on_slope_enabled"), &CharacterBody2D::is_stop_on_slope_enabled); - ClassDB::bind_method(D_METHOD("set_stop_on_slope_enabled", "enabled"), &CharacterBody2D::set_stop_on_slope_enabled); - ClassDB::bind_method(D_METHOD("is_infinite_inertia_enabled"), &CharacterBody2D::is_infinite_inertia_enabled); - ClassDB::bind_method(D_METHOD("set_infinite_inertia_enabled", "enabled"), &CharacterBody2D::set_infinite_inertia_enabled); + ClassDB::bind_method(D_METHOD("is_floor_stop_on_slope_enabled"), &CharacterBody2D::is_floor_stop_on_slope_enabled); + ClassDB::bind_method(D_METHOD("set_floor_stop_on_slope_enabled", "enabled"), &CharacterBody2D::set_floor_stop_on_slope_enabled); + ClassDB::bind_method(D_METHOD("set_floor_constant_speed_enabled", "enabled"), &CharacterBody2D::set_floor_constant_speed_enabled); + ClassDB::bind_method(D_METHOD("is_floor_constant_speed_enabled"), &CharacterBody2D::is_floor_constant_speed_enabled); + ClassDB::bind_method(D_METHOD("set_floor_block_on_wall_enabled", "enabled"), &CharacterBody2D::set_floor_block_on_wall_enabled); + ClassDB::bind_method(D_METHOD("is_floor_block_on_wall_enabled"), &CharacterBody2D::is_floor_block_on_wall_enabled); + ClassDB::bind_method(D_METHOD("set_slide_on_ceiling_enabled", "enabled"), &CharacterBody2D::set_slide_on_ceiling_enabled); + ClassDB::bind_method(D_METHOD("is_slide_on_ceiling_enabled"), &CharacterBody2D::is_slide_on_ceiling_enabled); + + ClassDB::bind_method(D_METHOD("set_moving_platform_floor_layers", "exclude_layer"), &CharacterBody2D::set_moving_platform_floor_layers); + ClassDB::bind_method(D_METHOD("get_moving_platform_floor_layers"), &CharacterBody2D::get_moving_platform_floor_layers); + ClassDB::bind_method(D_METHOD("set_moving_platform_wall_layers", "exclude_layer"), &CharacterBody2D::set_moving_platform_wall_layers); + ClassDB::bind_method(D_METHOD("get_moving_platform_wall_layers"), &CharacterBody2D::get_moving_platform_wall_layers); + ClassDB::bind_method(D_METHOD("get_max_slides"), &CharacterBody2D::get_max_slides); ClassDB::bind_method(D_METHOD("set_max_slides", "max_slides"), &CharacterBody2D::set_max_slides); ClassDB::bind_method(D_METHOD("get_floor_max_angle"), &CharacterBody2D::get_floor_max_angle); ClassDB::bind_method(D_METHOD("set_floor_max_angle", "radians"), &CharacterBody2D::set_floor_max_angle); - ClassDB::bind_method(D_METHOD("get_snap"), &CharacterBody2D::get_snap); - ClassDB::bind_method(D_METHOD("set_snap", "snap"), &CharacterBody2D::set_snap); + ClassDB::bind_method(D_METHOD("get_floor_snap_length"), &CharacterBody2D::get_floor_snap_length); + ClassDB::bind_method(D_METHOD("set_floor_snap_length", "floor_snap_length"), &CharacterBody2D::set_floor_snap_length); + ClassDB::bind_method(D_METHOD("get_free_mode_min_slide_angle"), &CharacterBody2D::get_free_mode_min_slide_angle); + ClassDB::bind_method(D_METHOD("set_free_mode_min_slide_angle", "radians"), &CharacterBody2D::set_free_mode_min_slide_angle); ClassDB::bind_method(D_METHOD("get_up_direction"), &CharacterBody2D::get_up_direction); ClassDB::bind_method(D_METHOD("set_up_direction", "up_direction"), &CharacterBody2D::set_up_direction); + ClassDB::bind_method(D_METHOD("set_motion_mode", "mode"), &CharacterBody2D::set_motion_mode); + ClassDB::bind_method(D_METHOD("get_motion_mode"), &CharacterBody2D::get_motion_mode); ClassDB::bind_method(D_METHOD("is_on_floor"), &CharacterBody2D::is_on_floor); + ClassDB::bind_method(D_METHOD("is_on_floor_only"), &CharacterBody2D::is_on_floor_only); ClassDB::bind_method(D_METHOD("is_on_ceiling"), &CharacterBody2D::is_on_ceiling); + ClassDB::bind_method(D_METHOD("is_on_ceiling_only"), &CharacterBody2D::is_on_ceiling_only); ClassDB::bind_method(D_METHOD("is_on_wall"), &CharacterBody2D::is_on_wall); + ClassDB::bind_method(D_METHOD("is_on_wall_only"), &CharacterBody2D::is_on_wall_only); ClassDB::bind_method(D_METHOD("get_floor_normal"), &CharacterBody2D::get_floor_normal); - ClassDB::bind_method(D_METHOD("get_floor_velocity"), &CharacterBody2D::get_floor_velocity); - ClassDB::bind_method(D_METHOD("get_slide_count"), &CharacterBody2D::get_slide_count); + ClassDB::bind_method(D_METHOD("get_floor_angle", "up_direction"), &CharacterBody2D::get_floor_angle, DEFVAL(Vector2(0.0, -1.0))); + ClassDB::bind_method(D_METHOD("get_platform_velocity"), &CharacterBody2D::get_platform_velocity); + ClassDB::bind_method(D_METHOD("get_slide_collision_count"), &CharacterBody2D::get_slide_collision_count); ClassDB::bind_method(D_METHOD("get_slide_collision", "slide_idx"), &CharacterBody2D::_get_slide_collision); + ClassDB::bind_method(D_METHOD("get_last_slide_collision"), &CharacterBody2D::_get_last_slide_collision); - ClassDB::bind_method(D_METHOD("set_sync_to_physics", "enable"), &CharacterBody2D::set_sync_to_physics); - ClassDB::bind_method(D_METHOD("is_sync_to_physics_enabled"), &CharacterBody2D::is_sync_to_physics_enabled); - + ADD_PROPERTY(PropertyInfo(Variant::INT, "motion_mode", PROPERTY_HINT_ENUM, "Grounded,Free", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_motion_mode", "get_motion_mode"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "linear_velocity"), "set_linear_velocity", "get_linear_velocity"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stop_on_slope"), "set_stop_on_slope_enabled", "is_stop_on_slope_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "infinite_inertia"), "set_infinite_inertia_enabled", "is_infinite_inertia_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "max_slides"), "set_max_slides", "get_max_slides"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_max_angle", PROPERTY_HINT_RANGE, "0,180,0.1"), "set_floor_max_angle", "get_floor_max_angle"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "snap"), "set_snap", "get_snap"); + 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_NOEDITOR), "set_max_slides", "get_max_slides"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "up_direction"), "set_up_direction", "get_up_direction"); - - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "motion/sync_to_physics"), "set_sync_to_physics", "is_sync_to_physics_enabled"); + ADD_GROUP("Free Mode", "free_mode_"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "free_mode_min_slide_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians", PROPERTY_USAGE_DEFAULT), "set_free_mode_min_slide_angle", "get_free_mode_min_slide_angle"); + ADD_GROUP("Floor", "floor_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_stop_on_slope"), "set_floor_stop_on_slope_enabled", "is_floor_stop_on_slope_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_constant_speed"), "set_floor_constant_speed_enabled", "is_floor_constant_speed_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_block_on_wall"), "set_floor_block_on_wall_enabled", "is_floor_block_on_wall_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_max_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians"), "set_floor_max_angle", "get_floor_max_angle"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_snap_length", PROPERTY_HINT_RANGE, "0,1000,0.1"), "set_floor_snap_length", "get_floor_snap_length"); + ADD_GROUP("Moving platform", "moving_platform"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "moving_platform_floor_layers", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_moving_platform_floor_layers", "get_moving_platform_floor_layers"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "moving_platform_wall_layers", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_moving_platform_wall_layers", "get_moving_platform_wall_layers"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision/safe_margin", PROPERTY_HINT_RANGE, "0.001,256,0.001"), "set_safe_margin", "get_safe_margin"); + + BIND_ENUM_CONSTANT(MOTION_MODE_GROUNDED); + BIND_ENUM_CONSTANT(MOTION_MODE_FREE); +} + +void CharacterBody2D::_validate_property(PropertyInfo &property) const { + if (motion_mode == MOTION_MODE_FREE) { + if (property.name.begins_with("floor_") || property.name == "up_direction" || property.name == "slide_on_ceiling") { + property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL; + } + } else { + if (property.name == "free_mode_min_slide_angle") { + property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL; + } + } } CharacterBody2D::CharacterBody2D() : @@ -1356,13 +1615,18 @@ Vector2 KinematicCollision2D::get_normal() const { } Vector2 KinematicCollision2D::get_travel() const { - return result.motion; + return result.travel; } Vector2 KinematicCollision2D::get_remainder() const { return result.remainder; } +real_t KinematicCollision2D::get_angle(const Vector2 &p_up_direction) const { + ERR_FAIL_COND_V(p_up_direction == Vector2(), 0); + return result.get_angle(p_up_direction); +} + Object *KinematicCollision2D::get_local_shape() const { if (!owner) { return nullptr; @@ -1417,6 +1681,7 @@ void KinematicCollision2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_normal"), &KinematicCollision2D::get_normal); ClassDB::bind_method(D_METHOD("get_travel"), &KinematicCollision2D::get_travel); ClassDB::bind_method(D_METHOD("get_remainder"), &KinematicCollision2D::get_remainder); + ClassDB::bind_method(D_METHOD("get_angle", "up_direction"), &KinematicCollision2D::get_angle, DEFVAL(Vector2(0.0, -1.0))); ClassDB::bind_method(D_METHOD("get_local_shape"), &KinematicCollision2D::get_local_shape); ClassDB::bind_method(D_METHOD("get_collider"), &KinematicCollision2D::get_collider); ClassDB::bind_method(D_METHOD("get_collider_id"), &KinematicCollision2D::get_collider_id); diff --git a/scene/2d/physics_body_2d.h b/scene/2d/physics_body_2d.h index 5a5417eaf3..1d6437a3ad 100644 --- a/scene/2d/physics_body_2d.h +++ b/scene/2d/physics_body_2d.h @@ -47,11 +47,11 @@ protected: Ref<KinematicCollision2D> motion_cache; - Ref<KinematicCollision2D> _move(const Vector2 &p_motion, bool p_infinite_inertia = true, bool p_exclude_raycast_shapes = true, bool p_test_only = false, real_t p_margin = 0.08); + Ref<KinematicCollision2D> _move(const Vector2 &p_motion, bool p_test_only = false, real_t p_margin = 0.08); public: - bool move_and_collide(const Vector2 &p_motion, bool p_infinite_inertia, PhysicsServer2D::MotionResult &r_result, real_t p_margin, bool p_exclude_raycast_shapes = true, bool p_test_only = false, bool p_cancel_sliding = true); - bool test_move(const Transform2D &p_from, const Vector2 &p_motion, bool p_infinite_inertia = true, bool p_exclude_raycast_shapes = true, const Ref<KinematicCollision2D> &r_collision = Ref<KinematicCollision2D>(), real_t p_margin = 0.08); + bool move_and_collide(const Vector2 &p_motion, PhysicsServer2D::MotionResult &r_result, real_t p_margin, bool p_test_only = false, bool p_cancel_sliding = true, bool p_collide_separation_ray = false, const Set<RID> &p_exclude = Set<RID>()); + bool test_move(const Transform2D &p_from, const Vector2 &p_motion, const Ref<KinematicCollision2D> &r_collision = Ref<KinematicCollision2D>(), real_t p_margin = 0.08); TypedArray<PhysicsBody2D> get_collision_exceptions(); void add_collision_exception_with(Node *p_node); //must be physicsbody @@ -63,15 +63,13 @@ public: class StaticBody2D : public PhysicsBody2D { GDCLASS(StaticBody2D, PhysicsBody2D); +private: Vector2 constant_linear_velocity; real_t constant_angular_velocity = 0.0; Ref<PhysicsMaterial> physics_material_override; - bool kinematic_motion = false; - protected: - void _notification(int p_what); static void _bind_methods(); public: @@ -84,15 +82,35 @@ public: Vector2 get_constant_linear_velocity() const; real_t get_constant_angular_velocity() const; - StaticBody2D(); + StaticBody2D(PhysicsServer2D::BodyMode p_mode = PhysicsServer2D::BODY_MODE_STATIC); private: void _reload_physics_characteristics(); +}; + +class AnimatableBody2D : public StaticBody2D { + GDCLASS(AnimatableBody2D, StaticBody2D); + +private: + bool sync_to_physics = false; + + Transform2D last_valid_transform; + static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState2D *p_state); + void _body_state_changed(PhysicsDirectBodyState2D *p_state); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + AnimatableBody2D(); + +private: void _update_kinematic_motion(); - void set_kinematic_motion_enabled(bool p_enabled); - bool is_kinematic_motion_enabled() const; + void set_sync_to_physics(bool p_enable); + bool is_sync_to_physics_enabled() const; }; class RigidBody2D : public PhysicsBody2D { @@ -106,6 +124,11 @@ public: MODE_KINEMATIC, }; + enum CenterOfMassMode { + CENTER_OF_MASS_MODE_AUTO, + CENTER_OF_MASS_MODE_CUSTOM, + }; + enum CCDMode { CCD_MODE_DISABLED, CCD_MODE_CAST_RAY, @@ -114,10 +137,13 @@ public: private: bool can_sleep = true; - PhysicsDirectBodyState2D *state = nullptr; Mode mode = MODE_DYNAMIC; real_t mass = 1.0; + real_t inertia = 0.0; + CenterOfMassMode center_of_mass_mode = CENTER_OF_MASS_MODE_AUTO; + Vector2 center_of_mass; + Ref<PhysicsMaterial> physics_material_override; real_t gravity_scale = 1.0; real_t linear_damp = -1.0; @@ -173,12 +199,18 @@ private: void _body_exit_tree(ObjectID p_id); void _body_inout(int p_status, const RID &p_body, ObjectID p_instance, int p_body_shape, int p_local_shape); - void _direct_state_changed(Object *p_state); + + static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState2D *p_state); + void _body_state_changed(PhysicsDirectBodyState2D *p_state); protected: void _notification(int p_what); static void _bind_methods(); + virtual void _validate_property(PropertyInfo &property) const override; + + GDVIRTUAL1(_integrate_forces, PhysicsDirectBodyState2D *) + public: void set_mode(Mode p_mode); Mode get_mode() const; @@ -189,6 +221,12 @@ public: void set_inertia(real_t p_inertia); real_t get_inertia() const; + void set_center_of_mass_mode(CenterOfMassMode p_mode); + CenterOfMassMode get_center_of_mass_mode() const; + + void set_center_of_mass(const Vector2 &p_center_of_mass); + const Vector2 &get_center_of_mass() const; + void set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override); Ref<PhysicsMaterial> get_physics_material_override() const; @@ -243,7 +281,7 @@ public: TypedArray<Node2D> get_colliding_bodies() const; //function for script - TypedArray<String> get_configuration_warnings() const override; + virtual TypedArray<String> get_configuration_warnings() const override; RigidBody2D(); ~RigidBody2D(); @@ -253,49 +291,80 @@ private: }; VARIANT_ENUM_CAST(RigidBody2D::Mode); +VARIANT_ENUM_CAST(RigidBody2D::CenterOfMassMode); VARIANT_ENUM_CAST(RigidBody2D::CCDMode); class CharacterBody2D : public PhysicsBody2D { GDCLASS(CharacterBody2D, PhysicsBody2D); +public: + enum MotionMode { + MOTION_MODE_GROUNDED, + MOTION_MODE_FREE, + }; + bool move_and_slide(); + + const Vector2 &get_linear_velocity() const; + void set_linear_velocity(const Vector2 &p_velocity); + + bool is_on_floor() const; + bool is_on_floor_only() const; + bool is_on_wall() const; + bool is_on_wall_only() const; + bool is_on_ceiling() const; + bool is_on_ceiling_only() const; + Vector2 get_floor_normal() const; + real_t get_floor_angle(const Vector2 &p_up_direction = Vector2(0.0, -1.0)) const; + Vector2 get_platform_velocity() const; + + int get_slide_collision_count() const; + PhysicsServer2D::MotionResult get_slide_collision(int p_bounce) const; + + CharacterBody2D(); + ~CharacterBody2D(); + private: real_t margin = 0.08; + MotionMode motion_mode = MOTION_MODE_GROUNDED; - bool stop_on_slope = false; - bool infinite_inertia = true; + bool floor_stop_on_slope = false; + bool floor_constant_speed = false; + bool floor_block_on_wall = true; + bool slide_on_ceiling = true; int max_slides = 4; + int platform_layer; real_t floor_max_angle = Math::deg2rad((real_t)45.0); - Vector2 snap; + float floor_snap_length = 0; + real_t free_mode_min_slide_angle = Math::deg2rad((real_t)15.0); Vector2 up_direction = Vector2(0.0, -1.0); - + uint32_t moving_platform_floor_layers = UINT32_MAX; + uint32_t moving_platform_wall_layers = 0; Vector2 linear_velocity; Vector2 floor_normal; - Vector2 floor_velocity; - RID on_floor_body; + Vector2 platform_velocity; + RID platform_rid; bool on_floor = false; bool on_ceiling = false; bool on_wall = false; - bool sync_to_physics = false; Vector<PhysicsServer2D::MotionResult> motion_results; Vector<Ref<KinematicCollision2D>> slide_colliders; - Ref<KinematicCollision2D> _get_slide_collision(int p_bounce); - - bool separate_raycast_shapes(PhysicsServer2D::MotionResult &r_result); - - Transform2D last_valid_transform; - void _direct_state_changed(Object *p_state); - void set_safe_margin(real_t p_margin); real_t get_safe_margin() const; - bool is_stop_on_slope_enabled() const; - void set_stop_on_slope_enabled(bool p_enabled); + bool is_floor_stop_on_slope_enabled() const; + void set_floor_stop_on_slope_enabled(bool p_enabled); + + bool is_floor_constant_speed_enabled() const; + void set_floor_constant_speed_enabled(bool p_enabled); - bool is_infinite_inertia_enabled() const; - void set_infinite_inertia_enabled(bool p_enabled); + bool is_floor_block_on_wall_enabled() const; + void set_floor_block_on_wall_enabled(bool p_enabled); + + bool is_slide_on_ceiling_enabled() const; + void set_slide_on_ceiling_enabled(bool p_enabled); int get_max_slides() const; void set_max_slides(int p_max_slides); @@ -303,38 +372,41 @@ private: real_t get_floor_max_angle() const; void set_floor_max_angle(real_t p_radians); - const Vector2 &get_snap() const; - void set_snap(const Vector2 &p_snap); + real_t get_floor_snap_length(); + void set_floor_snap_length(real_t p_floor_snap_length); - const Vector2 &get_up_direction() const; - void set_up_direction(const Vector2 &p_up_direction); + real_t get_free_mode_min_slide_angle() const; + void set_free_mode_min_slide_angle(real_t p_radians); -protected: - void _notification(int p_what); - static void _bind_methods(); + uint32_t get_moving_platform_floor_layers() const; + void set_moving_platform_floor_layers(const uint32_t p_exclude_layer); -public: - void move_and_slide(); + uint32_t get_moving_platform_wall_layers() const; + void set_moving_platform_wall_layers(const uint32_t p_exclude_layer); - const Vector2 &get_linear_velocity() const; - void set_linear_velocity(const Vector2 &p_velocity); - - bool is_on_floor() const; - bool is_on_wall() const; - bool is_on_ceiling() const; - Vector2 get_floor_normal() const; - Vector2 get_floor_velocity() const; + void set_motion_mode(MotionMode p_mode); + MotionMode get_motion_mode() const; - int get_slide_count() const; - PhysicsServer2D::MotionResult get_slide_collision(int p_bounce) const; + void _move_and_slide_free(double p_delta); + void _move_and_slide_grounded(double p_delta, bool p_was_on_floor, const Vector2 &p_prev_platform_velocity); - void set_sync_to_physics(bool p_enable); - bool is_sync_to_physics_enabled() const; + 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); + 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); - CharacterBody2D(); - ~CharacterBody2D(); +protected: + void _notification(int p_what); + static void _bind_methods(); + virtual void _validate_property(PropertyInfo &property) const override; }; +VARIANT_ENUM_CAST(CharacterBody2D::MotionMode); + class KinematicCollision2D : public RefCounted { GDCLASS(KinematicCollision2D, RefCounted); @@ -351,6 +423,7 @@ public: Vector2 get_normal() const; Vector2 get_travel() const; Vector2 get_remainder() const; + real_t get_angle(const Vector2 &p_up_direction = Vector2(0.0, -1.0)) const; Object *get_local_shape() const; Object *get_collider() const; ObjectID get_collider_id() const; diff --git a/scene/2d/position_2d.cpp b/scene/2d/position_2d.cpp index 1019f85c8a..4f053ff8b0 100644 --- a/scene/2d/position_2d.cpp +++ b/scene/2d/position_2d.cpp @@ -30,9 +30,6 @@ #include "position_2d.h" -#include "core/config/engine.h" -#include "scene/resources/texture.h" - const real_t DEFAULT_GIZMO_EXTENTS = 10.0; void Position2D::_draw_cross() { diff --git a/scene/2d/ray_cast_2d.cpp b/scene/2d/ray_cast_2d.cpp index f6740040c1..3ac2128c2e 100644 --- a/scene/2d/ray_cast_2d.cpp +++ b/scene/2d/ray_cast_2d.cpp @@ -31,9 +31,6 @@ #include "ray_cast_2d.h" #include "collision_object_2d.h" -#include "core/config/engine.h" -#include "physics_body_2d.h" -#include "servers/physics_server_2d.h" void RayCast2D::set_target_position(const Vector2 &p_point) { target_position = p_point; @@ -54,20 +51,22 @@ uint32_t RayCast2D::get_collision_mask() const { return collision_mask; } -void RayCast2D::set_collision_mask_bit(int p_bit, bool p_value) { - ERR_FAIL_INDEX_MSG(p_bit, 32, "Collision mask bit must be between 0 and 31 inclusive."); +void RayCast2D::set_collision_mask_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive."); uint32_t mask = get_collision_mask(); if (p_value) { - mask |= 1 << p_bit; + mask |= 1 << (p_layer_number - 1); } else { - mask &= ~(1 << p_bit); + mask &= ~(1 << (p_layer_number - 1)); } set_collision_mask(mask); } -bool RayCast2D::get_collision_mask_bit(int p_bit) const { - ERR_FAIL_INDEX_V_MSG(p_bit, 32, false, "Collision mask bit must be between 0 and 31 inclusive."); - return get_collision_mask() & (1 << p_bit); +bool RayCast2D::get_collision_mask_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Collision layer number must be between 1 and 32 inclusive."); + return get_collision_mask() & (1 << (p_layer_number - 1)); } bool RayCast2D::is_colliding() const { @@ -212,17 +211,17 @@ void RayCast2D::_update_raycast_state() { void RayCast2D::_draw_debug_shape() { Color draw_col = collided ? Color(1.0, 0.01, 0) : get_tree()->get_debug_collisions_color(); if (!enabled) { - float g = draw_col.get_v(); + const float g = draw_col.get_v(); draw_col.r = g; draw_col.g = g; draw_col.b = g; } // Draw an arrow indicating where the RayCast is pointing to - const float max_arrow_size = 6; - const float line_width = 1.4; + const real_t max_arrow_size = 6; + const real_t line_width = 1.4; bool no_line = target_position.length() < line_width; - float arrow_size = CLAMP(target_position.length() * 2 / 3, line_width, max_arrow_size); + real_t arrow_size = CLAMP(target_position.length() * 2 / 3, line_width, max_arrow_size); if (no_line) { arrow_size = target_position.length(); @@ -323,8 +322,8 @@ void RayCast2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &RayCast2D::set_collision_mask); ClassDB::bind_method(D_METHOD("get_collision_mask"), &RayCast2D::get_collision_mask); - ClassDB::bind_method(D_METHOD("set_collision_mask_bit", "bit", "value"), &RayCast2D::set_collision_mask_bit); - ClassDB::bind_method(D_METHOD("get_collision_mask_bit", "bit"), &RayCast2D::get_collision_mask_bit); + ClassDB::bind_method(D_METHOD("set_collision_mask_value", "layer_number", "value"), &RayCast2D::set_collision_mask_value); + ClassDB::bind_method(D_METHOD("get_collision_mask_value", "layer_number"), &RayCast2D::get_collision_mask_value); ClassDB::bind_method(D_METHOD("set_exclude_parent_body", "mask"), &RayCast2D::set_exclude_parent_body); ClassDB::bind_method(D_METHOD("get_exclude_parent_body"), &RayCast2D::get_exclude_parent_body); diff --git a/scene/2d/ray_cast_2d.h b/scene/2d/ray_cast_2d.h index 984c6bda49..65b6e7899b 100644 --- a/scene/2d/ray_cast_2d.h +++ b/scene/2d/ray_cast_2d.h @@ -74,8 +74,8 @@ public: void set_collision_mask(uint32_t p_mask); uint32_t get_collision_mask() const; - void set_collision_mask_bit(int p_bit, bool p_value); - bool get_collision_mask_bit(int p_bit) const; + void set_collision_mask_value(int p_layer_number, bool p_value); + bool get_collision_mask_value(int p_layer_number) const; void set_exclude_parent_body(bool p_exclude_parent_body); bool get_exclude_parent_body() const; diff --git a/scene/2d/remote_transform_2d.cpp b/scene/2d/remote_transform_2d.cpp index e7cef965fe..fe3e867424 100644 --- a/scene/2d/remote_transform_2d.cpp +++ b/scene/2d/remote_transform_2d.cpp @@ -29,7 +29,6 @@ /*************************************************************************/ #include "remote_transform_2d.h" -#include "scene/scene_string_names.h" void RemoteTransform2D::_update_cache() { cache = ObjectID(); diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp index 8f1f5fadbc..4bbbc3575d 100644 --- a/scene/2d/skeleton_2d.cpp +++ b/scene/2d/skeleton_2d.cpp @@ -30,8 +30,6 @@ #include "skeleton_2d.h" -#include "scene/resources/skeleton_modification_2d.h" - #ifdef TOOLS_ENABLED #include "editor/editor_settings.h" #include "editor/plugins/canvas_item_editor_plugin.h" @@ -45,7 +43,7 @@ bool Bone2D::_set(const StringName &p_path, const Variant &p_value) { } else if (path.begins_with("length")) { set_length(p_value); } else if (path.begins_with("bone_angle")) { - set_bone_angle(Math::deg2rad(float(p_value))); + set_bone_angle(Math::deg2rad(real_t(p_value))); } else if (path.begins_with("default_length")) { set_length(p_value); } @@ -327,10 +325,10 @@ bool Bone2D::_editor_get_bone_shape(Vector<Vector2> *p_shape, Vector<Vector2> *p Vector2 rel; if (p_other_bone) { - rel = (p_other_bone->get_global_transform().get_origin() - get_global_transform().get_origin()); + rel = (p_other_bone->get_global_position() - get_global_position()); rel = rel.rotated(-get_global_rotation()); // Undo Bone2D node's rotation so its drawn correctly regardless of the node's rotation } else { - float angle_to_use = get_rotation() + bone_angle; + real_t angle_to_use = get_rotation() + bone_angle; rel = Vector2(cos(angle_to_use), sin(angle_to_use)) * (length * MIN(get_global_scale().x, get_global_scale().y)); rel = rel.rotated(-get_rotation()); // Undo Bone2D node's rotation so its drawn correctly regardless of the node's rotation } @@ -414,12 +412,12 @@ void Bone2D::apply_rest() { set_transform(rest); } -void Bone2D::set_default_length(float p_length) { +void Bone2D::set_default_length(real_t p_length) { WARN_DEPRECATED_MSG("set_default_length is deprecated. Please use set_length instead!"); set_length(p_length); } -float Bone2D::get_default_length() const { +real_t Bone2D::get_default_length() const { WARN_DEPRECATED_MSG("get_default_length is deprecated. Please use get_length instead!"); return get_length(); } @@ -456,7 +454,7 @@ void Bone2D::calculate_length_and_rotation() { for (int i = 0; i < child_count; i++) { Bone2D *child = Object::cast_to<Bone2D>(get_child(i)); if (child) { - Vector2 child_local_pos = to_local(child->get_global_transform().get_origin()); + Vector2 child_local_pos = to_local(child->get_global_position()); length = child_local_pos.length(); bone_angle = Math::atan2(child_local_pos.normalized().y, child_local_pos.normalized().x); calculated = true; @@ -485,7 +483,7 @@ bool Bone2D::get_autocalculate_length_and_angle() const { return autocalculate_length_and_angle; } -void Bone2D::set_length(float p_length) { +void Bone2D::set_length(real_t p_length) { length = p_length; #ifdef TOOLS_ENABLED @@ -493,11 +491,11 @@ void Bone2D::set_length(float p_length) { #endif // TOOLS_ENABLED } -float Bone2D::get_length() const { +real_t Bone2D::get_length() const { return length; } -void Bone2D::set_bone_angle(float p_angle) { +void Bone2D::set_bone_angle(real_t p_angle) { bone_angle = p_angle; #ifdef TOOLS_ENABLED @@ -505,7 +503,7 @@ void Bone2D::set_bone_angle(float p_angle) { #endif // TOOLS_ENABLED } -float Bone2D::get_bone_angle() const { +real_t Bone2D::get_bone_angle() const { return bone_angle; } @@ -568,7 +566,7 @@ void Skeleton2D::_make_bone_setup_dirty() { } bone_setup_dirty = true; if (is_inside_tree()) { - call_deferred("_update_bone_setup"); + call_deferred(SNAME("_update_bone_setup")); } } @@ -597,7 +595,7 @@ void Skeleton2D::_update_bone_setup() { transform_dirty = true; _update_transform(); - emit_signal("bone_setup_changed"); + emit_signal(SNAME("bone_setup_changed")); } void Skeleton2D::_make_transform_dirty() { @@ -606,7 +604,7 @@ void Skeleton2D::_make_transform_dirty() { } transform_dirty = true; if (is_inside_tree()) { - call_deferred("_update_transform"); + call_deferred(SNAME("_update_transform")); } } @@ -690,7 +688,7 @@ RID Skeleton2D::get_skeleton() const { return skeleton; } -void Skeleton2D::set_bone_local_pose_override(int p_bone_idx, Transform2D p_override, float p_amount, bool p_persistent) { +void Skeleton2D::set_bone_local_pose_override(int p_bone_idx, Transform2D p_override, real_t p_amount, bool p_persistent) { ERR_FAIL_INDEX_MSG(p_bone_idx, bones.size(), "Bone index is out of range!"); bones.write[p_bone_idx].local_pose_override = p_override; bones.write[p_bone_idx].local_pose_override_amount = p_amount; @@ -728,7 +726,7 @@ Ref<SkeletonModificationStack2D> Skeleton2D::get_modification_stack() const { return modification_stack; } -void Skeleton2D::execute_modifications(float p_delta, int p_execution_mode) { +void Skeleton2D::execute_modifications(real_t p_delta, int p_execution_mode) { if (!modification_stack.is_valid()) { return; } diff --git a/scene/2d/skeleton_2d.h b/scene/2d/skeleton_2d.h index 59bd711960..56fd0e8504 100644 --- a/scene/2d/skeleton_2d.h +++ b/scene/2d/skeleton_2d.h @@ -49,8 +49,8 @@ class Bone2D : public Node2D { Transform2D rest; bool autocalculate_length_and_angle = true; - float length = 16; - float bone_angle = 0; + real_t length = 16; + real_t bone_angle = 0; int skeleton_index = -1; @@ -85,10 +85,10 @@ public: void set_autocalculate_length_and_angle(bool p_autocalculate); bool get_autocalculate_length_and_angle() const; - void set_length(float p_length); - float get_length() const; - void set_bone_angle(float p_angle); - float get_bone_angle() const; + void set_length(real_t p_length); + real_t get_length() const; + void set_bone_angle(real_t p_angle); + real_t get_bone_angle() const; int get_index_in_skeleton() const; @@ -122,7 +122,7 @@ class Skeleton2D : public Node2D { //Transform2D local_pose_cache; Transform2D local_pose_override; - float local_pose_override_amount = 0; + real_t local_pose_override_amount = 0; bool local_pose_override_persistent = false; }; @@ -153,12 +153,12 @@ public: RID get_skeleton() const; - void set_bone_local_pose_override(int p_bone_idx, Transform2D p_override, float p_amount, bool p_persistent = true); + void set_bone_local_pose_override(int p_bone_idx, Transform2D p_override, real_t p_amount, bool p_persistent = true); Transform2D get_bone_local_pose_override(int p_bone_idx); Ref<SkeletonModificationStack2D> get_modification_stack() const; void set_modification_stack(Ref<SkeletonModificationStack2D> p_stack); - void execute_modifications(float p_delta, int p_execution_mode); + void execute_modifications(real_t p_delta, int p_execution_mode); Skeleton2D(); ~Skeleton2D(); diff --git a/scene/2d/sprite_2d.cpp b/scene/2d/sprite_2d.cpp index 40e0f4523f..5761f19a53 100644 --- a/scene/2d/sprite_2d.cpp +++ b/scene/2d/sprite_2d.cpp @@ -31,7 +31,6 @@ #include "sprite_2d.h" #include "core/core_string_names.h" -#include "core/os/os.h" #include "scene/main/window.h" #include "scene/scene_string_names.h" diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index e39c8841cd..a139a92ab4 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -31,8 +31,8 @@ #include "tile_map.h" #include "core/io/marshalls.h" -#include "core/math/geometry_2d.h" -#include "core/os/os.h" + +#include "servers/navigation_server_2d.h" void TileMapPattern::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) { ERR_FAIL_COND_MSG(p_coords.x < 0 || p_coords.y < 0, vformat("Cannot set cell with negative coords in a TileMapPattern. Wrong coords: %s", p_coords)); @@ -58,7 +58,7 @@ void TileMapPattern::remove_cell(const Vector2i &p_coords, bool p_update_size) { } int TileMapPattern::get_cell_source_id(const Vector2i &p_coords) const { - ERR_FAIL_COND_V(!pattern.has(p_coords), -1); + ERR_FAIL_COND_V(!pattern.has(p_coords), TileSet::INVALID_SOURCE); return pattern[p_coords].source_id; } @@ -113,7 +113,7 @@ void TileMapPattern::clear() { }; void TileMapPattern::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMapPattern::set_cell, DEFVAL(-1), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetSource::INVALID_TILE_ALTERNATIVE)); + ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMapPattern::set_cell, DEFVAL(TileSet::INVALID_SOURCE), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetSource::INVALID_TILE_ALTERNATIVE)); ClassDB::bind_method(D_METHOD("has_cell", "coords"), &TileMapPattern::has_cell); ClassDB::bind_method(D_METHOD("remove_cell", "coords"), &TileMapPattern::remove_cell); ClassDB::bind_method(D_METHOD("get_cell_source_id", "coords"), &TileMapPattern::get_cell_source_id); @@ -235,40 +235,45 @@ Vector2i TileMap::transform_coords_layout(Vector2i p_coords, TileSet::TileOffset return output; } -int TileMap::get_effective_quadrant_size() const { +int TileMap::get_effective_quadrant_size(int p_layer) const { + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), 1); + // When using YSort, the quadrant size is reduced to 1 to have one CanvasItem per quadrant - if (tile_set.is_valid() && tile_set->is_y_sorting()) { + if (is_y_sort_enabled() && layers[p_layer].y_sort_enabled) { return 1; } else { return quadrant_size; } } -Vector2i TileMap::_coords_to_quadrant_coords(const Vector2i &p_coords) const { - int quadrant_size = get_effective_quadrant_size(); +void TileMap::set_selected_layer(int p_layer_id) { + ERR_FAIL_COND(p_layer_id < -1 || p_layer_id >= (int)layers.size()); + selected_layer = p_layer_id; + emit_signal(SNAME("changed")); + _make_all_quadrants_dirty(); +} - // Rounding down, instead of simply rounding towards zero (truncating) - return Vector2i( - p_coords.x > 0 ? p_coords.x / quadrant_size : (p_coords.x - (quadrant_size - 1)) / quadrant_size, - p_coords.y > 0 ? p_coords.y / quadrant_size : (p_coords.y - (quadrant_size - 1)) / quadrant_size); +int TileMap::get_selected_layer() const { + return selected_layer; } void TileMap::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { pending_update = true; - _recreate_quadrants(); + _clear_internals(); + _recreate_internals(); } break; case NOTIFICATION_EXIT_TREE: { - _clear_quadrants(); + _clear_internals(); } break; } // Transfers the notification to tileset plugins. if (tile_set.is_valid()) { - for (int i = 0; i < tile_set->get_tile_set_atlas_plugins().size(); i++) { - tile_set->get_tile_set_atlas_plugins()[i]->tilemap_notification(this, p_what); - } + _rendering_notification(p_what); + _physics_notification(p_what); + _navigation_notification(p_what); } } @@ -283,64 +288,262 @@ void TileMap::set_tileset(const Ref<TileSet> &p_tileset) { // Set the tileset, registering to its changes. if (tile_set.is_valid()) { - tile_set->disconnect("changed", callable_mp(this, &TileMap::_make_all_quadrants_dirty)); tile_set->disconnect("changed", callable_mp(this, &TileMap::_tile_set_changed)); } if (!p_tileset.is_valid()) { - _clear_quadrants(); + _clear_internals(); } tile_set = p_tileset; if (tile_set.is_valid()) { - tile_set->connect("changed", callable_mp(this, &TileMap::_make_all_quadrants_dirty), varray(true)); tile_set->connect("changed", callable_mp(this, &TileMap::_tile_set_changed)); - _recreate_quadrants(); + _clear_internals(); + _recreate_internals(); } - emit_signal("changed"); + emit_signal(SNAME("changed")); +} + +void TileMap::set_quadrant_size(int p_size) { + ERR_FAIL_COND_MSG(p_size < 1, "TileMapQuadrant size cannot be smaller than 1."); + + quadrant_size = p_size; + _clear_internals(); + _recreate_internals(); + emit_signal(SNAME("changed")); } int TileMap::get_quadrant_size() const { return quadrant_size; } -void TileMap::set_quadrant_size(int p_size) { - ERR_FAIL_COND_MSG(p_size < 1, "TileMapQuadrant size cannot be smaller than 1."); +int TileMap::get_layers_count() const { + return layers.size(); +} - quadrant_size = p_size; - _recreate_quadrants(); - emit_signal("changed"); +void TileMap::add_layer(int p_to_pos) { + if (p_to_pos < 0) { + p_to_pos = layers.size(); + } + + ERR_FAIL_INDEX(p_to_pos, (int)layers.size() + 1); + + // Must clear before adding the layer. + _clear_internals(); + + layers.insert(p_to_pos, TileMapLayer()); + _recreate_internals(); + notify_property_list_changed(); + + emit_signal(SNAME("changed")); + + update_configuration_warnings(); +} + +void TileMap::move_layer(int p_layer, int p_to_pos) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); + ERR_FAIL_INDEX(p_to_pos, (int)layers.size() + 1); + + // Clear before shuffling layers. + _clear_internals(); + + TileMapLayer tl = layers[p_layer]; + layers.insert(p_to_pos, tl); + layers.remove(p_to_pos < p_layer ? p_layer + 1 : p_layer); + _recreate_internals(); + notify_property_list_changed(); + + if (selected_layer == p_layer) { + selected_layer = p_to_pos < p_layer ? p_to_pos - 1 : p_to_pos; + } + + emit_signal(SNAME("changed")); + + update_configuration_warnings(); +} + +void TileMap::remove_layer(int p_layer) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); + + // Clear before removing the layer. + _clear_internals(); + + layers.remove(p_layer); + _recreate_internals(); + notify_property_list_changed(); + + if (selected_layer >= p_layer) { + selected_layer -= 1; + } + + emit_signal(SNAME("changed")); + + update_configuration_warnings(); +} + +void TileMap::set_layer_name(int p_layer, String p_name) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); + layers[p_layer].name = p_name; + emit_signal(SNAME("changed")); +} + +String TileMap::get_layer_name(int p_layer) const { + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), String()); + return layers[p_layer].name; +} + +void TileMap::set_layer_enabled(int p_layer, bool p_enabled) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); + layers[p_layer].enabled = p_enabled; + _clear_internals(); + _recreate_internals(); + emit_signal(SNAME("changed")); + + update_configuration_warnings(); +} + +bool TileMap::is_layer_enabled(int p_layer) const { + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), false); + return layers[p_layer].enabled; +} + +void TileMap::set_layer_y_sort_enabled(int p_layer, bool p_y_sort_enabled) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); + layers[p_layer].y_sort_enabled = p_y_sort_enabled; + _clear_internals(); + _recreate_internals(); + emit_signal(SNAME("changed")); + + update_configuration_warnings(); +} + +bool TileMap::is_layer_y_sort_enabled(int p_layer) const { + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), false); + return layers[p_layer].y_sort_enabled; +} + +void TileMap::set_layer_y_sort_origin(int p_layer, int p_y_sort_origin) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); + layers[p_layer].y_sort_origin = p_y_sort_origin; + _clear_internals(); + _recreate_internals(); + emit_signal(SNAME("changed")); +} + +int TileMap::get_layer_y_sort_origin(int p_layer) const { + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), false); + return layers[p_layer].y_sort_origin; +} + +void TileMap::set_layer_z_index(int p_layer, int p_z_index) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); + layers[p_layer].z_index = p_z_index; + _clear_internals(); + _recreate_internals(); + emit_signal(SNAME("changed")); + + update_configuration_warnings(); +} + +int TileMap::get_layer_z_index(int p_layer) const { + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), false); + return layers[p_layer].z_index; } void TileMap::set_collision_visibility_mode(TileMap::VisibilityMode p_show_collision) { - show_collision = p_show_collision; - _recreate_quadrants(); - emit_signal("changed"); + collision_visibility_mode = p_show_collision; + _clear_internals(); + _recreate_internals(); + emit_signal(SNAME("changed")); } TileMap::VisibilityMode TileMap::get_collision_visibility_mode() { - return show_collision; + return collision_visibility_mode; } void TileMap::set_navigation_visibility_mode(TileMap::VisibilityMode p_show_navigation) { - show_navigation = p_show_navigation; - _recreate_quadrants(); - emit_signal("changed"); + navigation_visibility_mode = p_show_navigation; + _clear_internals(); + _recreate_internals(); + emit_signal(SNAME("changed")); } TileMap::VisibilityMode TileMap::get_navigation_visibility_mode() { - return show_navigation; + return navigation_visibility_mode; } void TileMap::set_y_sort_enabled(bool p_enable) { Node2D::set_y_sort_enabled(p_enable); - _recreate_quadrants(); - emit_signal("changed"); + _clear_internals(); + _recreate_internals(); + emit_signal(SNAME("changed")); } -void TileMap::update_dirty_quadrants() { +Vector2i TileMap::_coords_to_quadrant_coords(int p_layer, const Vector2i &p_coords) const { + int quadrant_size = get_effective_quadrant_size(p_layer); + + // Rounding down, instead of simply rounding towards zero (truncating) + return Vector2i( + p_coords.x > 0 ? p_coords.x / quadrant_size : (p_coords.x - (quadrant_size - 1)) / quadrant_size, + 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) { + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), nullptr); + + TileMapQuadrant q; + q.layer = p_layer; + q.coords = p_qk; + + rect_cache_dirty = true; + + // Create the debug canvas item. + RenderingServer *rs = RenderingServer::get_singleton(); + q.debug_canvas_item = rs->canvas_item_create(); + rs->canvas_item_set_z_index(q.debug_canvas_item, RS::CANVAS_ITEM_Z_MAX - 1); + rs->canvas_item_set_parent(q.debug_canvas_item, get_canvas_item()); + + // Call the create_quadrant method on plugins + if (tile_set.is_valid()) { + _rendering_create_quadrant(&q); + _physics_create_quadrant(&q); + } + + return layers[p_layer].quadrant_map.insert(p_qk, q); +} + +void TileMap::_make_quadrant_dirty(Map<Vector2i, TileMapQuadrant>::Element *Q) { + // Make the given quadrant dirty, then trigger an update later. + TileMapQuadrant &q = Q->get(); + if (!q.dirty_list_element.in_list()) { + layers[q.layer].dirty_quadrant_list.add(&q.dirty_list_element); + } + _queue_update_dirty_quadrants(); +} + +void TileMap::_make_all_quadrants_dirty() { + // Make all quandrants dirty, then trigger an update later. + for (unsigned int layer = 0; layer < layers.size(); layer++) { + for (Map<Vector2i, TileMapQuadrant>::Element *E = layers[layer].quadrant_map.front(); E; E = E->next()) { + if (!E->value().dirty_list_element.in_list()) { + layers[layer].dirty_quadrant_list.add(&E->value().dirty_list_element); + } + } + } + _queue_update_dirty_quadrants(); +} + +void TileMap::_queue_update_dirty_quadrants() { + if (pending_update || !is_inside_tree()) { + return; + } + pending_update = true; + call_deferred(SNAME("_update_dirty_quadrants")); +} + +void TileMap::_update_dirty_quadrants() { if (!pending_update) { return; } @@ -349,43 +552,130 @@ void TileMap::update_dirty_quadrants() { return; } - // Update the coords cache. - 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(); - 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; + for (unsigned int layer = 0; layer < layers.size(); layer++) { + // Update the coords cache. + for (SelfList<TileMapQuadrant> *q = layers[layer].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(); + 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; + } } - } - // Call the update_dirty_quadrant method on plugins. - for (int i = 0; i < tile_set->get_tile_set_atlas_plugins().size(); i++) { - tile_set->get_tile_set_atlas_plugins()[i]->update_dirty_quadrants(this, dirty_quadrant_list); + // Call the update_dirty_quadrant method on plugins. + _rendering_update_dirty_quadrants(layers[layer].dirty_quadrant_list); + _physics_update_dirty_quadrants(layers[layer].dirty_quadrant_list); + _navigation_update_dirty_quadrants(layers[layer].dirty_quadrant_list); + _scenes_update_dirty_quadrants(layers[layer].dirty_quadrant_list); + + // Redraw the debug canvas_items. + RenderingServer *rs = RenderingServer::get_singleton(); + for (SelfList<TileMapQuadrant> *q = layers[layer].dirty_quadrant_list.first(); q; q = q->next()) { + rs->canvas_item_clear(q->self()->debug_canvas_item); + Transform2D xform; + xform.set_origin(map_to_world(q->self()->coords * get_effective_quadrant_size(layer))); + rs->canvas_item_set_transform(q->self()->debug_canvas_item, xform); + + _rendering_draw_quadrant_debug(q->self()); + _physics_draw_quadrant_debug(q->self()); + _navigation_draw_quadrant_debug(q->self()); + _scenes_draw_quadrant_debug(q->self()); + } + + // Clear the list + while (layers[layer].dirty_quadrant_list.first()) { + layers[layer].dirty_quadrant_list.remove(layers[layer].dirty_quadrant_list.first()); + } } - // Redraw the debug canvas_items. - RenderingServer *rs = RenderingServer::get_singleton(); - for (SelfList<TileMapQuadrant> *q = dirty_quadrant_list.first(); q; q = q->next()) { - rs->canvas_item_clear(q->self()->debug_canvas_item); - Transform2D xform; - xform.set_origin(map_to_world(q->self()->coords * get_effective_quadrant_size())); - rs->canvas_item_set_transform(q->self()->debug_canvas_item, xform); - for (int i = 0; i < tile_set->get_tile_set_atlas_plugins().size(); i++) { - tile_set->get_tile_set_atlas_plugins()[i]->draw_quadrant_debug(this, q->self()); + pending_update = false; + + _recompute_rect_cache(); +} + +void TileMap::_recreate_internals() { + for (unsigned int layer = 0; layer < layers.size(); layer++) { + // Make sure that _clear_internals() was called prior. + ERR_FAIL_COND_MSG(layers[layer].quadrant_map.size() > 0, "TileMap layer " + itos(layer) + " had a non-empty quadrant map."); + + if (!layers[layer].enabled) { + continue; + } + + // Upadate the layer internals. + _rendering_update_layer(layer); + + // Recreate the quadrants. + const Map<Vector2i, TileMapCell> &tile_map = layers[layer].tile_map; + for (Map<Vector2i, TileMapCell>::Element *E = tile_map.front(); E; E = E->next()) { + Vector2i qk = _coords_to_quadrant_coords(layer, Vector2i(E->key().x, E->key().y)); + + Map<Vector2i, TileMapQuadrant>::Element *Q = layers[layer].quadrant_map.find(qk); + if (!Q) { + Q = _create_quadrant(layer, qk); + layers[layer].dirty_quadrant_list.add(&Q->get().dirty_list_element); + } + + Vector2i pk = E->key(); + Q->get().cells.insert(pk); + + _make_quadrant_dirty(Q); } } - // Clear the list - while (dirty_quadrant_list.first()) { - dirty_quadrant_list.remove(dirty_quadrant_list.first()); + _update_dirty_quadrants(); +} + +void TileMap::_erase_quadrant(Map<Vector2i, TileMapQuadrant>::Element *Q) { + // Remove a quadrant. + TileMapQuadrant *q = &(Q->get()); + + // Call the cleanup_quadrant method on plugins. + if (tile_set.is_valid()) { + _rendering_cleanup_quadrant(q); + _physics_cleanup_quadrant(q); + _navigation_cleanup_quadrant(q); + _scenes_cleanup_quadrant(q); } - pending_update = false; + // Remove the quadrant from the dirty_list if it is there. + if (q->dirty_list_element.in_list()) { + layers[q->layer].dirty_quadrant_list.remove(&(q->dirty_list_element)); + } - _recompute_rect_cache(); + // Free the debug canvas item. + RenderingServer *rs = RenderingServer::get_singleton(); + rs->free(q->debug_canvas_item); + + layers[q->layer].quadrant_map.erase(Q); + rect_cache_dirty = true; +} + +void TileMap::_clear_layer_internals(int p_layer) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); + + // Clear quadrants. + while (layers[p_layer].quadrant_map.size()) { + _erase_quadrant(layers[p_layer].quadrant_map.front()); + } + + // Clear the layers internals. + _rendering_cleanup_layer(p_layer); + + // Clear the dirty quadrants list. + while (layers[p_layer].dirty_quadrant_list.first()) { + layers[p_layer].dirty_quadrant_list.remove(layers[p_layer].dirty_quadrant_list.first()); + } +} + +void TileMap::_clear_internals() { + // Clear quadrants. + for (unsigned int layer = 0; layer < layers.size(); layer++) { + _clear_layer_internals(layer); + } } void TileMap::_recompute_rect_cache() { @@ -397,16 +687,18 @@ void TileMap::_recompute_rect_cache() { } Rect2 r_total; - for (Map<Vector2i, TileMapQuadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { - Rect2 r; - r.position = map_to_world(E->key() * get_effective_quadrant_size()); - r.expand_to(map_to_world((E->key() + Vector2i(1, 0)) * get_effective_quadrant_size())); - r.expand_to(map_to_world((E->key() + Vector2i(1, 1)) * get_effective_quadrant_size())); - r.expand_to(map_to_world((E->key() + Vector2i(0, 1)) * get_effective_quadrant_size())); - if (E == quadrant_map.front()) { - r_total = r; - } else { - r_total = r_total.merge(r); + for (unsigned int layer = 0; layer < layers.size(); layer++) { + for (Map<Vector2i, TileMapQuadrant>::Element *E = layers[layer].quadrant_map.front(); E; E = E->next()) { + 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_total = r; + } else { + r_total = r_total.merge(r); + } } } @@ -418,94 +710,892 @@ void TileMap::_recompute_rect_cache() { #endif } -Map<Vector2i, TileMapQuadrant>::Element *TileMap::_create_quadrant(const Vector2i &p_qk) { - TileMapQuadrant q; - q.coords = p_qk; +/////////////////////////////// Rendering ////////////////////////////////////// - rect_cache_dirty = true; +void TileMap::_rendering_notification(int p_what) { + switch (p_what) { + case CanvasItem::NOTIFICATION_VISIBILITY_CHANGED: { + bool visible = is_visible_in_tree(); + for (int layer = 0; layer < (int)layers.size(); layer++) { + for (Map<Vector2i, TileMapQuadrant>::Element *E_quadrant = layers[layer].quadrant_map.front(); E_quadrant; E_quadrant = E_quadrant->next()) { + TileMapQuadrant &q = E_quadrant->get(); + + // Update occluders transform. + for (Map<Vector2i, Vector2i, TileMapQuadrant::CoordsWorldComparator>::Element *E_cell = q.world_to_map.front(); E_cell; E_cell = E_cell->next()) { + Transform2D xform; + xform.set_origin(E_cell->key()); + for (const RID &occluder : q.occluders) { + RS::get_singleton()->canvas_light_occluder_set_enabled(occluder, visible); + } + } + } + } + } break; + case CanvasItem::NOTIFICATION_TRANSFORM_CHANGED: { + if (!is_inside_tree()) { + return; + } + for (int layer = 0; layer < (int)layers.size(); layer++) { + for (Map<Vector2i, TileMapQuadrant>::Element *E_quadrant = layers[layer].quadrant_map.front(); E_quadrant; E_quadrant = E_quadrant->next()) { + TileMapQuadrant &q = E_quadrant->get(); + + // Update occluders transform. + for (Map<Vector2i, Vector2i, TileMapQuadrant::CoordsWorldComparator>::Element *E_cell = q.world_to_map.front(); E_cell; E_cell = E_cell->next()) { + Transform2D xform; + xform.set_origin(E_cell->key()); + for (const RID &occluder : q.occluders) { + RS::get_singleton()->canvas_light_occluder_set_transform(occluder, get_global_transform() * xform); + } + } + } + } + } break; + case CanvasItem::NOTIFICATION_DRAW: { + if (tile_set.is_valid()) { + RenderingServer::get_singleton()->canvas_item_set_sort_children_by_y(get_canvas_item(), is_y_sort_enabled()); + } + } break; + } +} + +void TileMap::_rendering_update_layer(int p_layer) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); - // Create the debug canvas item. RenderingServer *rs = RenderingServer::get_singleton(); - q.debug_canvas_item = rs->canvas_item_create(); - rs->canvas_item_set_z_index(q.debug_canvas_item, RS::CANVAS_ITEM_Z_MAX - 1); - rs->canvas_item_set_parent(q.debug_canvas_item, get_canvas_item()); + if (!layers[p_layer].canvas_item.is_valid()) { + RID ci = rs->canvas_item_create(); + rs->canvas_item_set_parent(ci, get_canvas_item()); - // Call the create_quadrant method on plugins - if (tile_set.is_valid()) { - for (int i = 0; i < tile_set->get_tile_set_atlas_plugins().size(); i++) { - tile_set->get_tile_set_atlas_plugins()[i]->create_quadrant(this, &q); - } + /*Transform2D xform; + xform.set_origin(Vector2(0, p_layer)); + rs->canvas_item_set_transform(ci, xform);*/ + rs->canvas_item_set_draw_index(ci, p_layer); + + layers[p_layer].canvas_item = ci; } + RID &ci = layers[p_layer].canvas_item; + rs->canvas_item_set_sort_children_by_y(ci, layers[p_layer].y_sort_enabled); + rs->canvas_item_set_use_parent_material(ci, get_use_parent_material() || get_material().is_valid()); + rs->canvas_item_set_z_index(ci, layers[p_layer].z_index); + rs->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(get_texture_filter())); + rs->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(get_texture_repeat())); + rs->canvas_item_set_light_mask(ci, get_light_mask()); +} - return quadrant_map.insert(p_qk, q); +void TileMap::_rendering_cleanup_layer(int p_layer) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); + + RenderingServer *rs = RenderingServer::get_singleton(); + if (!layers[p_layer].canvas_item.is_valid()) { + rs->free(layers[p_layer].canvas_item); + } } -void TileMap::_erase_quadrant(Map<Vector2i, TileMapQuadrant>::Element *Q) { - // Remove a quadrant. - TileMapQuadrant *q = &(Q->get()); +void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { + ERR_FAIL_COND(!is_inside_tree()); + ERR_FAIL_COND(!tile_set.is_valid()); - // Call the cleanup_quadrant method on plugins. - if (tile_set.is_valid()) { - for (int i = 0; i < tile_set->get_tile_set_atlas_plugins().size(); i++) { - tile_set->get_tile_set_atlas_plugins()[i]->cleanup_quadrant(this, q); + bool visible = is_visible_in_tree(); + + SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); + while (q_list_element) { + TileMapQuadrant &q = *q_list_element->self(); + + RenderingServer *rs = RenderingServer::get_singleton(); + + // Free the canvas items. + for (const RID &ci : q.canvas_items) { + rs->free(ci); + } + q.canvas_items.clear(); + + // Free the occluders. + for (const RID &occluder : q.occluders) { + rs->free(occluder); + } + q.occluders.clear(); + + // Those allow to group cell per material or z-index. + Ref<ShaderMaterial> prev_material; + int prev_z_index = 0; + RID prev_canvas_item; + + // Iterate over the cells of the quadrant. + for (Map<Vector2i, Vector2i, TileMapQuadrant::CoordsWorldComparator>::Element *E_cell = q.world_to_map.front(); E_cell; E_cell = E_cell->next()) { + TileMapCell c = get_cell(q.layer, E_cell->value(), true); + + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); + + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } + + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + // Get the tile data. + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); + Ref<ShaderMaterial> mat = tile_data->get_material(); + int z_index = tile_data->get_z_index(); + + // Quandrant pos. + Vector2 position = map_to_world(q.coords * get_effective_quadrant_size(q.layer)); + if (is_y_sort_enabled() && layers[q.layer].y_sort_enabled) { + // When Y-sorting, the quandrant size is sure to be 1, we can thus offset the CanvasItem. + position.y += layers[q.layer].y_sort_origin + tile_data->get_y_sort_origin(); + } + + // --- CanvasItems --- + // Create two canvas items, for rendering and debug. + RID canvas_item; + + // Check if the material or the z_index changed. + if (prev_canvas_item == RID() || prev_material != mat || prev_z_index != z_index) { + // If so, create a new CanvasItem. + canvas_item = rs->canvas_item_create(); + if (mat.is_valid()) { + rs->canvas_item_set_material(canvas_item, mat->get_rid()); + } + rs->canvas_item_set_parent(canvas_item, layers[q.layer].canvas_item); + rs->canvas_item_set_use_parent_material(canvas_item, get_use_parent_material() || get_material().is_valid()); + + Transform2D xform; + xform.set_origin(position); + rs->canvas_item_set_transform(canvas_item, xform); + + rs->canvas_item_set_light_mask(canvas_item, get_light_mask()); + rs->canvas_item_set_z_index(canvas_item, z_index); + + rs->canvas_item_set_default_texture_filter(canvas_item, RS::CanvasItemTextureFilter(get_texture_filter())); + rs->canvas_item_set_default_texture_repeat(canvas_item, RS::CanvasItemTextureRepeat(get_texture_repeat())); + + q.canvas_items.push_back(canvas_item); + + prev_canvas_item = canvas_item; + prev_material = mat; + prev_z_index = z_index; + + } else { + // Keep the same canvas_item to draw on. + canvas_item = prev_canvas_item; + } + + // Drawing the tile in the canvas item. + Color modulate = get_self_modulate(); + if (selected_layer >= 0) { + if (q.layer < selected_layer) { + modulate = modulate.darkened(0.5); + } else if (q.layer > selected_layer) { + modulate = modulate.darkened(0.5); + modulate.a *= 0.3; + } + } + draw_tile(canvas_item, E_cell->key() - position, tile_set, c.source_id, c.get_atlas_coords(), c.alternative_tile, modulate); + + // --- Occluders --- + for (int i = 0; i < tile_set->get_occlusion_layers_count(); i++) { + Transform2D xform; + xform.set_origin(E_cell->key()); + if (tile_data->get_occluder(i).is_valid()) { + RID occluder_id = rs->canvas_light_occluder_create(); + rs->canvas_light_occluder_set_enabled(occluder_id, visible); + rs->canvas_light_occluder_set_transform(occluder_id, get_global_transform() * xform); + rs->canvas_light_occluder_set_polygon(occluder_id, tile_data->get_occluder(i)->get_rid()); + rs->canvas_light_occluder_attach_to_canvas(occluder_id, get_canvas()); + rs->canvas_light_occluder_set_light_mask(occluder_id, tile_set->get_occlusion_layer_light_mask(i)); + q.occluders.push_back(occluder_id); + } + } + } + } + } + + _rendering_quadrant_order_dirty = true; + q_list_element = q_list_element->next(); + } + + // Reset the drawing indices + if (_rendering_quadrant_order_dirty) { + int index = -(int64_t)0x80000000; //always must be drawn below children. + + for (int layer = 0; layer < (int)layers.size(); layer++) { + // Sort the quadrants coords per world coordinates + Map<Vector2i, Vector2i, TileMapQuadrant::CoordsWorldComparator> world_to_map; + for (Map<Vector2i, TileMapQuadrant>::Element *E = layers[layer].quadrant_map.front(); E; E = E->next()) { + world_to_map[map_to_world(E->key())] = E->key(); + } + + // Sort the quadrants + for (Map<Vector2i, Vector2i, TileMapQuadrant::CoordsWorldComparator>::Element *E = world_to_map.front(); E; E = E->next()) { + TileMapQuadrant &q = layers[layer].quadrant_map[E->value()]; + for (const RID &ci : q.canvas_items) { + RS::get_singleton()->canvas_item_set_draw_index(ci, index++); + } + } } + _rendering_quadrant_order_dirty = false; } +} - // Remove the quadrant from the dirty_list if it is there. - if (q->dirty_list_element.in_list()) { - dirty_quadrant_list.remove(&(q->dirty_list_element)); +void TileMap::_rendering_create_quadrant(TileMapQuadrant *p_quadrant) { + ERR_FAIL_COND(!tile_set.is_valid()); + + _rendering_quadrant_order_dirty = true; +} + +void TileMap::_rendering_cleanup_quadrant(TileMapQuadrant *p_quadrant) { + // Free the canvas items. + for (const RID &ci : p_quadrant->canvas_items) { + RenderingServer::get_singleton()->free(ci); } + p_quadrant->canvas_items.clear(); - // Free the debug canvas item. + // Free the occluders. + for (const RID &occluder : p_quadrant->occluders) { + RenderingServer::get_singleton()->free(occluder); + } + p_quadrant->occluders.clear(); +} + +void TileMap::_rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { + ERR_FAIL_COND(!tile_set.is_valid()); + + if (!Engine::get_singleton()->is_editor_hint()) { + return; + } + + // Draw a placeholder for scenes needing one. RenderingServer *rs = RenderingServer::get_singleton(); - rs->free(q->debug_canvas_item); + 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); - quadrant_map.erase(Q); - rect_cache_dirty = true; + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); + + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } + + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + Vector2i grid_size = atlas_source->get_atlas_grid_size(); + if (!atlas_source->get_texture().is_valid() || c.get_atlas_coords().x >= grid_size.x || c.get_atlas_coords().y >= grid_size.y) { + // Generate a random color from the hashed values of the tiles. + Array to_hash; + to_hash.push_back(c.source_id); + to_hash.push_back(c.get_atlas_coords()); + to_hash.push_back(c.alternative_tile); + uint32_t hash = RandomPCG(to_hash.hash()).rand(); + + Color color; + color = color.from_hsv( + (float)((hash >> 24) & 0xFF) / 256.0, + Math::lerp(0.5, 1.0, (float)((hash >> 16) & 0xFF) / 256.0), + Math::lerp(0.5, 1.0, (float)((hash >> 8) & 0xFF) / 256.0), + 0.8); + + // Draw a placeholder tile. + Transform2D xform; + xform.set_origin(map_to_world(E_cell->get()) - 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); + } + } + } + } } -void TileMap::_make_all_quadrants_dirty(bool p_update) { - // Make all quandrants dirty, then trigger an update later. - for (Map<Vector2i, TileMapQuadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { - if (!E->value().dirty_list_element.in_list()) { - dirty_quadrant_list.add(&E->value().dirty_list_element); +void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, Color p_modulation) { + ERR_FAIL_COND(!p_tile_set.is_valid()); + ERR_FAIL_COND(!p_tile_set->has_source(p_atlas_source_id)); + ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_tile(p_atlas_coords)); + ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_alternative_tile(p_atlas_coords, p_alternative_tile)); + + TileSetSource *source = *p_tile_set->get_source(p_atlas_source_id); + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + // Get the texture. + Ref<Texture2D> tex = atlas_source->get_texture(); + if (!tex.is_valid()) { + return; } + + // Check if we are in the texture, return otherwise. + Vector2i grid_size = atlas_source->get_atlas_grid_size(); + if (p_atlas_coords.x >= grid_size.x || p_atlas_coords.y >= grid_size.y) { + return; + } + + // Get tile data. + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile)); + + // Compute the offset + Rect2i source_rect = atlas_source->get_tile_texture_region(p_atlas_coords); + Vector2i tile_offset = atlas_source->get_tile_effective_texture_offset(p_atlas_coords, p_alternative_tile); + + // Compute the destination rectangle in the CanvasItem. + Rect2 dest_rect; + dest_rect.size = source_rect.size; + dest_rect.size.x += FP_ADJUST; + dest_rect.size.y += FP_ADJUST; + + bool transpose = tile_data->get_transpose(); + if (transpose) { + dest_rect.position = (p_position - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset); + } else { + dest_rect.position = (p_position - dest_rect.size / 2 - tile_offset); + } + + if (tile_data->get_flip_h()) { + dest_rect.size.x = -dest_rect.size.x; + } + + if (tile_data->get_flip_v()) { + dest_rect.size.y = -dest_rect.size.y; + } + + // Get the tile modulation. + Color modulate = tile_data->get_modulate(); + modulate = Color(modulate.r * p_modulation.r, modulate.g * p_modulation.g, modulate.b * p_modulation.b, modulate.a * p_modulation.a); + + // Draw the tile. + tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); } +} - if (pending_update) { +/////////////////////////////// Physics ////////////////////////////////////// + +void TileMap::_physics_notification(int p_what) { + switch (p_what) { + case CanvasItem::NOTIFICATION_TRANSFORM_CHANGED: { + // Update the bodies transforms. + if (is_inside_tree()) { + for (int layer = 0; layer < (int)layers.size(); layer++) { + Transform2D global_transform = get_global_transform(); + + for (Map<Vector2i, TileMapQuadrant>::Element *E = layers[layer].quadrant_map.front(); E; E = E->next()) { + TileMapQuadrant &q = E->get(); + + Transform2D xform; + xform.set_origin(map_to_world(E->key() * get_effective_quadrant_size(layer))); + xform = global_transform * xform; + + for (int body_index = 0; body_index < q.bodies.size(); body_index++) { + PhysicsServer2D::get_singleton()->body_set_state(q.bodies[body_index], PhysicsServer2D::BODY_STATE_TRANSFORM, xform); + } + } + } + } + } break; + } +} + +void TileMap::_physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { + ERR_FAIL_COND(!is_inside_tree()); + ERR_FAIL_COND(!tile_set.is_valid()); + + Transform2D global_transform = get_global_transform(); + PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); + + SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); + while (q_list_element) { + TileMapQuadrant &q = *q_list_element->self(); + + Vector2 quadrant_pos = map_to_world(q.coords * get_effective_quadrant_size(q.layer)); + + LocalVector<int> body_shape_count; + body_shape_count.resize(q.bodies.size()); + + // Clear shapes. + for (int body_index = 0; body_index < q.bodies.size(); body_index++) { + ps->body_clear_shapes(q.bodies[body_index]); + body_shape_count[body_index] = 0; + + // Position the bodies. + Transform2D xform; + xform.set_origin(quadrant_pos); + xform = global_transform * xform; + ps->body_set_state(q.bodies[body_index], PhysicsServer2D::BODY_STATE_TRANSFORM, xform); + } + + 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); + + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); + + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } + + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); + + for (int body_index = 0; body_index < q.bodies.size(); body_index++) { + int &body_shape_index = body_shape_count[body_index]; + + // Add the shapes again. + for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(body_index); polygon_index++) { + bool one_way_collision = tile_data->is_collision_polygon_one_way(body_index, polygon_index); + float one_way_collision_margin = tile_data->get_collision_polygon_one_way_margin(body_index, polygon_index); + + int shapes_count = tile_data->get_collision_polygon_shapes_count(body_index, polygon_index); + for (int shape_index = 0; shape_index < shapes_count; shape_index++) { + Transform2D xform = Transform2D(); + xform.set_origin(map_to_world(E_cell->get()) - quadrant_pos); + + // Add decomposed convex shapes. + Ref<ConvexPolygonShape2D> shape = tile_data->get_collision_polygon_shape(body_index, polygon_index, shape_index); + ps->body_add_shape(q.bodies[body_index], shape->get_rid(), xform); + ps->body_set_shape_metadata(q.bodies[body_index], body_shape_index, E_cell->get()); + ps->body_set_shape_as_one_way_collision(q.bodies[body_index], body_shape_index, one_way_collision, one_way_collision_margin); + + ++body_shape_index; + } + } + } + } + } + } + + q_list_element = q_list_element->next(); + } +} + +void TileMap::_physics_create_quadrant(TileMapQuadrant *p_quadrant) { + ERR_FAIL_COND(!tile_set.is_valid()); + + //Get the TileMap's gobla transform. + Transform2D global_transform; + if (is_inside_tree()) { + global_transform = get_global_transform(); + } + + // Clear all bodies. + p_quadrant->bodies.clear(); + + // Create the body and set its parameters. + for (int layer = 0; layer < tile_set->get_physics_layers_count(); layer++) { + RID body = PhysicsServer2D::get_singleton()->body_create(); + PhysicsServer2D::get_singleton()->body_set_mode(body, PhysicsServer2D::BODY_MODE_STATIC); + + PhysicsServer2D::get_singleton()->body_attach_object_instance_id(body, get_instance_id()); + PhysicsServer2D::get_singleton()->body_set_collision_layer(body, tile_set->get_physics_layer_collision_layer(layer)); + PhysicsServer2D::get_singleton()->body_set_collision_mask(body, tile_set->get_physics_layer_collision_mask(layer)); + + Ref<PhysicsMaterial> physics_material = tile_set->get_physics_layer_physics_material(layer); + if (!physics_material.is_valid()) { + PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, 0); + PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, 1); + } else { + PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, physics_material->computed_bounce()); + PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, physics_material->computed_friction()); + } + + if (is_inside_tree()) { + RID space = get_world_2d()->get_space(); + PhysicsServer2D::get_singleton()->body_set_space(body, space); + + Transform2D xform; + xform.set_origin(map_to_world(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer))); + xform = global_transform * xform; + PhysicsServer2D::get_singleton()->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); + } + + p_quadrant->bodies.push_back(body); + } +} + +void TileMap::_physics_cleanup_quadrant(TileMapQuadrant *p_quadrant) { + // Remove a quadrant. + for (int body_index = 0; body_index < p_quadrant->bodies.size(); body_index++) { + PhysicsServer2D::get_singleton()->free(p_quadrant->bodies[body_index]); + } + p_quadrant->bodies.clear(); +} + +void TileMap::_physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { + // Draw the debug collision shapes. + ERR_FAIL_COND(!tile_set.is_valid()); + + if (!get_tree()) { return; } - pending_update = true; - if (!is_inside_tree()) { + + bool show_collision = false; + switch (collision_visibility_mode) { + case TileMap::VISIBILITY_MODE_DEFAULT: + show_collision = !Engine::get_singleton()->is_editor_hint() && (get_tree() && get_tree()->is_debugging_navigation_hint()); + break; + case TileMap::VISIBILITY_MODE_FORCE_HIDE: + show_collision = false; + break; + case TileMap::VISIBILITY_MODE_FORCE_SHOW: + show_collision = true; + break; + } + if (!show_collision) { return; } - if (p_update) { - call_deferred("update_dirty_quadrants"); + + RenderingServer *rs = RenderingServer::get_singleton(); + + Vector2 quadrant_pos = map_to_world(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer)); + + Color debug_collision_color = get_tree()->get_debug_collisions_color(); + 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); + + Transform2D xform; + xform.set_origin(map_to_world(E_cell->get()) - quadrant_pos); + rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, xform); + + if (tile_set->has_source(c.source_id)) { + TileSetSource *source = *tile_set->get_source(c.source_id); + + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } + + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); + + for (int body_index = 0; body_index < p_quadrant->bodies.size(); body_index++) { + for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(body_index); polygon_index++) { + // Draw the debug polygon. + Vector<Vector2> polygon = tile_data->get_collision_polygon_points(body_index, polygon_index); + if (polygon.size() >= 3) { + Vector<Color> color; + color.push_back(debug_collision_color); + rs->canvas_item_add_polygon(p_quadrant->debug_canvas_item, polygon, color); + } + } + } + } + } + rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, Transform2D()); + } +}; + +/////////////////////////////// Navigation ////////////////////////////////////// + +void TileMap::_navigation_notification(int p_what) { + switch (p_what) { + case CanvasItem::NOTIFICATION_TRANSFORM_CHANGED: { + if (is_inside_tree()) { + for (int layer = 0; layer < (int)layers.size(); layer++) { + Transform2D tilemap_xform = get_global_transform(); + for (Map<Vector2i, TileMapQuadrant>::Element *E_quadrant = layers[layer].quadrant_map.front(); E_quadrant; E_quadrant = E_quadrant->next()) { + TileMapQuadrant &q = E_quadrant->get(); + for (Map<Vector2i, Vector<RID>>::Element *E_region = q.navigation_regions.front(); E_region; E_region = E_region->next()) { + for (int layer_index = 0; layer_index < E_region->get().size(); layer_index++) { + RID region = E_region->get()[layer_index]; + if (!region.is_valid()) { + continue; + } + Transform2D tile_transform; + tile_transform.set_origin(map_to_world(E_region->key())); + NavigationServer2D::get_singleton()->region_set_transform(region, tilemap_xform * tile_transform); + } + } + } + } + } + } break; } } -void TileMap::_make_quadrant_dirty(Map<Vector2i, TileMapQuadrant>::Element *Q, bool p_update) { - // Make the given quadrant dirty, then trigger an update later. - TileMapQuadrant &q = Q->get(); - if (!q.dirty_list_element.in_list()) { - dirty_quadrant_list.add(&q.dirty_list_element); +void TileMap::_navigation_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { + ERR_FAIL_COND(!is_inside_tree()); + ERR_FAIL_COND(!tile_set.is_valid()); + + // Get colors for debug. + SceneTree *st = SceneTree::get_singleton(); + Color debug_navigation_color; + bool debug_navigation = st && st->is_debugging_navigation_hint(); + if (debug_navigation) { + debug_navigation_color = st->get_debug_navigation_color(); + } + + Transform2D tilemap_xform = get_global_transform(); + SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); + while (q_list_element) { + TileMapQuadrant &q = *q_list_element->self(); + + // Clear navigation shapes in the quadrant. + for (Map<Vector2i, Vector<RID>>::Element *E = q.navigation_regions.front(); E; E = E->next()) { + for (int i = 0; i < E->get().size(); i++) { + RID region = E->get()[i]; + if (!region.is_valid()) { + continue; + } + NavigationServer2D::get_singleton()->region_set_map(region, RID()); + } + } + 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); + + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); + + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } + + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + TileData *tile_data = Object::cast_to<TileData>(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()); + + for (int layer_index = 0; layer_index < tile_set->get_navigation_layers_count(); layer_index++) { + Ref<NavigationPolygon> navpoly; + navpoly = tile_data->get_navigation_polygon(layer_index); + + if (navpoly.is_valid()) { + Transform2D tile_transform; + tile_transform.set_origin(map_to_world(E_cell->get())); + + 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_list_element = q_list_element->next(); } +} - if (pending_update) { +void TileMap::_navigation_cleanup_quadrant(TileMapQuadrant *p_quadrant) { + // Clear navigation shapes in the quadrant. + for (Map<Vector2i, Vector<RID>>::Element *E = p_quadrant->navigation_regions.front(); E; E = E->next()) { + for (int i = 0; i < E->get().size(); i++) { + RID region = E->get()[i]; + if (!region.is_valid()) { + continue; + } + NavigationServer2D::get_singleton()->free(region); + } + } + p_quadrant->navigation_regions.clear(); +} + +void TileMap::_navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { + // Draw the debug collision shapes. + ERR_FAIL_COND(!tile_set.is_valid()); + + if (!get_tree()) { return; } - pending_update = true; - if (!is_inside_tree()) { + + bool show_navigation = false; + switch (navigation_visibility_mode) { + case TileMap::VISIBILITY_MODE_DEFAULT: + show_navigation = !Engine::get_singleton()->is_editor_hint() && (get_tree() && get_tree()->is_debugging_navigation_hint()); + break; + case TileMap::VISIBILITY_MODE_FORCE_HIDE: + show_navigation = false; + break; + case TileMap::VISIBILITY_MODE_FORCE_SHOW: + show_navigation = true; + break; + } + if (!show_navigation) { return; } - if (p_update) { - call_deferred("update_dirty_quadrants"); + RenderingServer *rs = RenderingServer::get_singleton(); + + Color color = get_tree()->get_debug_navigation_color(); + RandomPCG rand; + + 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); + + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); + + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } + + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + TileData *tile_data = Object::cast_to<TileData>(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); + 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++) { + Ref<NavigationPolygon> navpoly = tile_data->get_navigation_polygon(layer_index); + if (navpoly.is_valid()) { + PackedVector2Array navigation_polygon_vertices = navpoly->get_vertices(); + + for (int i = 0; i < navpoly->get_polygon_count(); i++) { + // An array of vertices for this polygon. + Vector<int> polygon = navpoly->get_polygon(i); + Vector<Vector2> vertices; + vertices.resize(polygon.size()); + for (int j = 0; j < polygon.size(); j++) { + ERR_FAIL_INDEX(polygon[j], navigation_polygon_vertices.size()); + vertices.write[j] = navigation_polygon_vertices[polygon[j]]; + } + + // Generate the polygon color, slightly randomly modified from the settings one. + Color random_variation_color; + random_variation_color.set_hsv(color.get_h() + rand.random(-1.0, 1.0) * 0.05, color.get_s(), color.get_v() + rand.random(-1.0, 1.0) * 0.1); + random_variation_color.a = color.a; + Vector<Color> colors; + colors.push_back(random_variation_color); + + rs->canvas_item_add_polygon(p_quadrant->debug_canvas_item, vertices, colors); + } + } + } + } + } } } -void TileMap::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) { +/////////////////////////////// Scenes ////////////////////////////////////// + +void TileMap::_scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { + ERR_FAIL_COND(!tile_set.is_valid()); + + SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); + while (q_list_element) { + TileMapQuadrant &q = *q_list_element->self(); + + // Clear the scenes. + for (Map<Vector2i, String>::Element *E = q.scenes.front(); E; E = E->next()) { + Node *node = get_node(E->get()); + if (node) { + node->queue_delete(); + } + } + + 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); + + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); + + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } + + TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source); + if (scenes_collection_source) { + Ref<PackedScene> packed_scene = scenes_collection_source->get_scene_tile_scene(c.alternative_tile); + if (packed_scene.is_valid()) { + Node *scene = packed_scene->instantiate(); + add_child(scene); + 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()); + } else if (scene_as_node2d) { + Transform2D xform; + xform.set_origin(map_to_world(E_cell->get())); + scene_as_node2d->set_transform(xform * scene_as_node2d->get_transform()); + } + q.scenes[E_cell->get()] = scene->get_name(); + } + } + } + } + + q_list_element = q_list_element->next(); + } +} + +void TileMap::_scenes_cleanup_quadrant(TileMapQuadrant *p_quadrant) { + // Clear the scenes. + for (Map<Vector2i, String>::Element *E = p_quadrant->scenes.front(); E; E = E->next()) { + Node *node = get_node(E->get()); + if (node) { + node->queue_delete(); + } + } + + p_quadrant->scenes.clear(); +} + +void TileMap::_scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { + ERR_FAIL_COND(!tile_set.is_valid()); + + if (!Engine::get_singleton()->is_editor_hint()) { + return; + } + + // 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); + + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); + + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } + + TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source); + if (scenes_collection_source) { + if (!scenes_collection_source->get_scene_tile_scene(c.alternative_tile).is_valid() || scenes_collection_source->get_scene_tile_display_placeholder(c.alternative_tile)) { + // Generate a random color from the hashed values of the tiles. + Array to_hash; + to_hash.push_back(c.source_id); + to_hash.push_back(c.alternative_tile); + uint32_t hash = RandomPCG(to_hash.hash()).rand(); + + Color color; + color = color.from_hsv( + (float)((hash >> 24) & 0xFF) / 256.0, + Math::lerp(0.5, 1.0, (float)((hash >> 16) & 0xFF) / 256.0), + Math::lerp(0.5, 1.0, (float)((hash >> 8) & 0xFF) / 256.0), + 0.8); + + // Draw a placeholder tile. + Transform2D xform; + xform.set_origin(map_to_world(E_cell->get()) - 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); + } + } + } + } +} + +void TileMap::set_cell(int p_layer, const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) { + 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; Vector2i pk(p_coords); Map<Vector2i, TileMapCell>::Element *E = tile_map.find(pk); @@ -513,24 +1603,24 @@ void TileMap::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i Vector2i atlas_coords = p_atlas_coords; int alternative_tile = p_alternative_tile; - if ((source_id == -1 || atlas_coords == TileSetSource::INVALID_ATLAS_COORDS || alternative_tile == TileSetSource::INVALID_TILE_ALTERNATIVE) && - (source_id != -1 || atlas_coords != TileSetSource::INVALID_ATLAS_COORDS || alternative_tile != TileSetSource::INVALID_TILE_ALTERNATIVE)) { + if ((source_id == TileSet::INVALID_SOURCE || atlas_coords == TileSetSource::INVALID_ATLAS_COORDS || alternative_tile == TileSetSource::INVALID_TILE_ALTERNATIVE) && + (source_id != TileSet::INVALID_SOURCE || atlas_coords != TileSetSource::INVALID_ATLAS_COORDS || alternative_tile != TileSetSource::INVALID_TILE_ALTERNATIVE)) { WARN_PRINT("Setting a cell a cell as empty requires both source_id, atlas_coord and alternative_tile to be set to their respective \"invalid\" values. Values were thus changes accordingly."); - source_id = -1; + source_id = TileSet::INVALID_SOURCE; atlas_coords = TileSetSource::INVALID_ATLAS_COORDS; alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; } - if (!E && source_id == -1) { + if (!E && source_id == TileSet::INVALID_SOURCE) { return; // Nothing to do, the tile is already empty. } // Get the quadrant - Vector2i qk = _coords_to_quadrant_coords(pk); + Vector2i qk = _coords_to_quadrant_coords(p_layer, pk); - Map<Vector2i, TileMapQuadrant>::Element *Q = quadrant_map.find(qk); + Map<Vector2i, TileMapQuadrant>::Element *Q = layers[p_layer].quadrant_map.find(qk); - if (source_id == -1) { + if (source_id == TileSet::INVALID_SOURCE) { // Erase existing cell in the tile map. tile_map.erase(pk); @@ -547,7 +1637,7 @@ void TileMap::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i _make_quadrant_dirty(Q); } - used_size_cache_dirty = true; + used_rect_cache_dirty = true; } else { if (!E) { // Insert a new cell in the tile map. @@ -555,7 +1645,7 @@ void TileMap::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i // Create a new quadrant if needed, then insert the cell if needed. if (!Q) { - Q = _create_quadrant(qk); + Q = _create_quadrant(p_layer, qk); } TileMapQuadrant &q = Q->get(); q.cells.insert(pk); @@ -575,44 +1665,69 @@ void TileMap::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i c.alternative_tile = alternative_tile; _make_quadrant_dirty(Q); - used_size_cache_dirty = true; + used_rect_cache_dirty = true; } } -int TileMap::get_cell_source_id(const Vector2i &p_coords) const { +int TileMap::get_cell_source_id(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { + 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); if (!E) { - return -1; + 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); + return proxyed[0]; } return E->get().source_id; } -Vector2i TileMap::get_cell_atlas_coords(const Vector2i &p_coords) const { +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); 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); + return proxyed[1]; + } + return E->get().get_atlas_coords(); } -int TileMap::get_cell_alternative_tile(const Vector2i &p_coords) const { +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); 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); + return proxyed[2]; + } + return E->get().alternative_tile; } -TileMapPattern *TileMap::get_pattern(TypedArray<Vector2i> p_coords_array) { +TileMapPattern *TileMap::get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array) { + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), nullptr); ERR_FAIL_COND_V(!tile_set.is_valid(), nullptr); TileMapPattern *output = memnew(TileMapPattern); @@ -658,7 +1773,7 @@ TileMapPattern *TileMap::get_pattern(TypedArray<Vector2i> p_coords_array) { for (int i = 0; i < coords_in_pattern_array.size(); i++) { Vector2i coords = p_coords_array[i]; Vector2i coords_in_pattern = coords_in_pattern_array[i]; - output->set_cell(coords_in_pattern + ensure_positive_offset, get_cell_source_id(coords), get_cell_atlas_coords(coords), get_cell_alternative_tile(coords)); + output->set_cell(coords_in_pattern + ensure_positive_offset, get_cell_source_id(p_layer, coords), get_cell_atlas_coords(p_layer, coords), get_cell_alternative_tile(p_layer, coords)); } return output; @@ -687,94 +1802,90 @@ Vector2i TileMap::map_pattern(Vector2i p_position_in_tilemap, Vector2i p_coords_ return output; } -void TileMap::set_pattern(Vector2i p_position, const TileMapPattern *p_pattern) { +void TileMap::set_pattern(int p_layer, Vector2i p_position, const TileMapPattern *p_pattern) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); ERR_FAIL_COND(!tile_set.is_valid()); 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(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(coords), p_pattern->get_cell_atlas_coords(coords), p_pattern->get_cell_alternative_tile(coords)); } } -TileMapCell TileMap::get_cell(const Vector2i &p_coords) const { +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; if (!tile_map.has(p_coords)) { return TileMapCell(); } else { - return tile_map.find(p_coords)->get(); + TileMapCell c = tile_map.find(p_coords)->get(); + 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]; + c.set_atlas_coords(proxyed[1]); + c.alternative_tile = proxyed[2]; + } + return c; } } -Map<Vector2i, TileMapQuadrant> &TileMap::get_quadrant_map() { - return quadrant_map; +Map<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; } void TileMap::fix_invalid_tiles() { ERR_FAIL_COND_MSG(tile_set.is_null(), "Cannot fix invalid tiles if Tileset is not open."); - Set<Vector2i> coords; - for (Map<Vector2i, TileMapCell>::Element *E = tile_map.front(); E; E = E->next()) { - TileSetSource *source = *tile_set->get_source(E->get().source_id); - if (!source || !source->has_tile(E->get().get_atlas_coords()) || !source->has_alternative_tile(E->get().get_atlas_coords(), E->get().alternative_tile)) { - coords.insert(E->key()); + for (unsigned int i = 0; i < layers.size(); i++) { + const Map<Vector2i, TileMapCell> &tile_map = layers[i].tile_map; + Set<Vector2i> coords; + for (Map<Vector2i, TileMapCell>::Element *E = tile_map.front(); E; E = E->next()) { + TileSetSource *source = *tile_set->get_source(E->get().source_id); + if (!source || !source->has_tile(E->get().get_atlas_coords()) || !source->has_alternative_tile(E->get().get_atlas_coords(), E->get().alternative_tile)) { + coords.insert(E->key()); + } } - } - for (Set<Vector2i>::Element *E = coords.front(); E; E = E->next()) { - set_cell(E->get(), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); - } -} - -void TileMap::_recreate_quadrants() { - // Clear then recreate all quadrants. - _clear_quadrants(); - - for (Map<Vector2i, TileMapCell>::Element *E = tile_map.front(); E; E = E->next()) { - Vector2i qk = _coords_to_quadrant_coords(Vector2i(E->key().x, E->key().y)); - - Map<Vector2i, TileMapQuadrant>::Element *Q = quadrant_map.find(qk); - if (!Q) { - Q = _create_quadrant(qk); - dirty_quadrant_list.add(&Q->get().dirty_list_element); + 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); } - - Vector2i pk = E->key(); - Q->get().cells.insert(pk); - - _make_quadrant_dirty(Q, false); } - - update_dirty_quadrants(); } -void TileMap::_clear_quadrants() { - // Clear quadrants. - while (quadrant_map.size()) { - _erase_quadrant(quadrant_map.front()); - } +void TileMap::clear_layer(int p_layer) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); - // Clear the dirty quadrants list. - while (dirty_quadrant_list.first()) { - dirty_quadrant_list.remove(dirty_quadrant_list.first()); - } + // Remove all tiles. + _clear_layer_internals(p_layer); + layers[p_layer].tile_map.clear(); + + used_rect_cache_dirty = true; } void TileMap::clear() { // Remove all tiles. - _clear_quadrants(); - tile_map.clear(); - used_size_cache_dirty = true; + _clear_internals(); + for (unsigned int i = 0; i < layers.size(); i++) { + layers[i].tile_map.clear(); + } + used_rect_cache_dirty = true; } -void TileMap::_set_tile_data(const Vector<int> &p_data) { - // Set data for a given tile from raw data. +void TileMap::_set_tile_data(int p_layer, const Vector<int> &p_data) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); ERR_FAIL_COND(format > FORMAT_3); + // Set data for a given tile from raw data. + int c = p_data.size(); const int *r = p_data.ptr(); int offset = (format >= FORMAT_2) ? 3 : 2; + ERR_FAIL_COND_MSG(c % offset != 0, "Corrupted tile data."); - clear(); + clear_layer(p_layer); #ifdef DISABLE_DEPRECATED ERR_FAIL_COND_MSG(format != FORMAT_3, vformat("Cannot handle deprecated TileMap data format version %d. This Godot version was compiled with no support for deprecated data.", format)); @@ -799,25 +1910,28 @@ void TileMap::_set_tile_data(const Vector<int> &p_data) { SWAP(local[9], local[10]); } #endif + // Extracts position in TileMap. int16_t x = decode_uint16(&local[0]); int16_t y = decode_uint16(&local[2]); if (format == FORMAT_3) { uint16_t source_id = decode_uint16(&local[4]); uint16_t atlas_coords_x = decode_uint16(&local[6]); - uint16_t atlas_coords_y = decode_uint32(&local[8]); + uint16_t atlas_coords_y = decode_uint16(&local[8]); uint16_t alternative_tile = decode_uint16(&local[10]); - set_cell(Vector2i(x, y), source_id, Vector2i(atlas_coords_x, atlas_coords_y), alternative_tile); + set_cell(p_layer, Vector2i(x, y), source_id, Vector2i(atlas_coords_x, atlas_coords_y), alternative_tile); } else { #ifndef DISABLE_DEPRECATED - uint32_t v = decode_uint32(&local[4]); - v &= (1 << 29) - 1; + // Previous decated format. - // We generate an alternative tile number out of the the flags - // An option should create the alternative in the tileset for compatibility + 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; + + // Extract autotile/atlas coords. int16_t coord_x = 0; int16_t coord_y = 0; if (format == FORMAT_2) { @@ -825,20 +1939,28 @@ void TileMap::_set_tile_data(const Vector<int> &p_data) { coord_y = decode_uint16(&local[10]); } - int compatibility_alternative_tile = ((int)flip_h) + ((int)flip_v << 1) + ((int)transpose << 2); - if (tile_set.is_valid()) { - v = tile_set->compatibility_get_source_for_tile_id(v); + Array a = tile_set->compatibility_tilemap_map(v, Vector2i(coord_x, coord_y), flip_h, flip_v, transpose); + if (a.size() == 3) { + set_cell(p_layer, Vector2i(x, y), a[0], a[1], a[2]); + } else { + ERR_PRINT(vformat("No valid tile in Tileset for: tile:%s coords:%s flip_h:%s flip_v:%s transpose:%s", v, Vector2i(coord_x, coord_y), flip_h, flip_v, transpose)); + } + } else { + int compatibility_alternative_tile = ((int)flip_h) + ((int)flip_v << 1) + ((int)transpose << 2); + set_cell(p_layer, Vector2i(x, y), v, Vector2i(coord_x, coord_y), compatibility_alternative_tile); } - - set_cell(Vector2i(x, y), v, Vector2i(coord_x, coord_y), compatibility_alternative_tile); #endif } } + emit_signal(SNAME("changed")); } -Vector<int> TileMap::_get_tile_data() const { +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; Vector<int> data; data.resize(tile_map.size() * 3); int *w = data.ptrw(); @@ -864,7 +1986,7 @@ Vector<int> TileMap::_get_tile_data() const { Rect2 TileMap::_edit_get_rect() const { // Return the visible rect of the tilemap if (pending_update) { - const_cast<TileMap *>(this)->update_dirty_quadrants(); + const_cast<TileMap *>(this)->_update_dirty_quadrants(); } else { const_cast<TileMap *>(this)->_recompute_rect_cache(); } @@ -873,38 +1995,99 @@ Rect2 TileMap::_edit_get_rect() const { #endif bool TileMap::_set(const StringName &p_name, const Variant &p_value) { + Vector<String> components = String(p_name).split("/", true, 2); if (p_name == "format") { if (p_value.get_type() == Variant::INT) { format = (DataFormat)(p_value.operator int64_t()); // Set format used for loading return true; } - } else if (p_name == "tile_data") { + } else if (p_name == "tile_data") { // Kept for compatibility reasons. if (p_value.is_array()) { - _set_tile_data(p_value); + if (layers.size() < 1) { + layers.resize(1); + } + _set_tile_data(0, p_value); return true; } return false; + } else if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) { + int index = components[0].trim_prefix("layer_").to_int(); + if (index < 0 || index >= (int)layers.size()) { + return false; + } + + if (components[1] == "name") { + set_layer_name(index, p_value); + return true; + } else if (components[1] == "enabled") { + set_layer_enabled(index, p_value); + return true; + } else if (components[1] == "y_sort_enabled") { + set_layer_y_sort_enabled(index, p_value); + return true; + } else if (components[1] == "y_sort_origin") { + set_layer_y_sort_origin(index, p_value); + return true; + } else if (components[1] == "z_index") { + set_layer_z_index(index, p_value); + return true; + } else if (components[1] == "tile_data") { + _set_tile_data(index, p_value); + return true; + } else { + return false; + } } return false; } bool TileMap::_get(const StringName &p_name, Variant &r_ret) const { + Vector<String> components = String(p_name).split("/", true, 2); if (p_name == "format") { r_ret = FORMAT_3; // When saving, always save highest format return true; - } else if (p_name == "tile_data") { - r_ret = _get_tile_data(); - return true; + } else if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) { + int index = components[0].trim_prefix("layer_").to_int(); + if (index < 0 || index >= (int)layers.size()) { + return false; + } + + if (components[1] == "name") { + r_ret = get_layer_name(index); + return true; + } else if (components[1] == "enabled") { + r_ret = is_layer_enabled(index); + return true; + } else if (components[1] == "y_sort_enabled") { + r_ret = is_layer_y_sort_enabled(index); + return true; + } else if (components[1] == "y_sort_origin") { + r_ret = get_layer_y_sort_origin(index); + return true; + } else if (components[1] == "z_index") { + r_ret = get_layer_z_index(index); + return true; + } else if (components[1] == "tile_data") { + r_ret = _get_tile_data(index); + return true; + } else { + return false; + } } return false; } void TileMap::_get_property_list(List<PropertyInfo> *p_list) const { - PropertyInfo p(Variant::INT, "format", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL); - p_list->push_back(p); - - p = PropertyInfo(Variant::OBJECT, "tile_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL); - p_list->push_back(p); + p_list->push_back(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::NIL, "Layers", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); + for (unsigned int i = 0; i < layers.size(); i++) { + p_list->push_back(PropertyInfo(Variant::STRING, vformat("layer_%d/name", i), PROPERTY_HINT_NONE)); + p_list->push_back(PropertyInfo(Variant::BOOL, vformat("layer_%d/enabled", i), PROPERTY_HINT_NONE)); + p_list->push_back(PropertyInfo(Variant::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/z_index", i), PROPERTY_HINT_NONE)); + p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("layer_%d/tile_data", i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + } } Vector2 TileMap::map_to_world(const Vector2i &p_pos) const { @@ -1527,12 +2710,14 @@ Vector2i TileMap::get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeigh } } -TypedArray<Vector2i> TileMap::get_used_cells() const { +TypedArray<Vector2i> TileMap::get_used_cells(int p_layer) const { + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), TypedArray<Vector2i>()); + // Returns the cells used in the tilemap. TypedArray<Vector2i> a; - a.resize(tile_map.size()); + a.resize(layers[p_layer].tile_map.size()); int i = 0; - for (Map<Vector2i, TileMapCell>::Element *E = tile_map.front(); E; E = E->next()) { + for (Map<Vector2i, TileMapCell>::Element *E = layers[p_layer].tile_map.front(); E; E = E->next()) { Vector2i p(E->key().x, E->key().y); a[i++] = p; } @@ -1542,23 +2727,31 @@ TypedArray<Vector2i> TileMap::get_used_cells() const { Rect2 TileMap::get_used_rect() { // Not const because of cache // Return the rect of the currently used area - if (used_size_cache_dirty) { - if (tile_map.size() > 0) { - used_size_cache = Rect2(tile_map.front()->key().x, tile_map.front()->key().y, 0, 0); + if (used_rect_cache_dirty) { + bool first = true; + used_rect_cache = Rect2i(); + + for (unsigned int i = 0; i < layers.size(); i++) { + const Map<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); + first = false; + } - for (Map<Vector2i, TileMapCell>::Element *E = tile_map.front(); E; E = E->next()) { - used_size_cache.expand_to(Vector2(E->key().x, E->key().y)); + for (Map<Vector2i, TileMapCell>::Element *E = tile_map.front(); E; E = E->next()) { + used_rect_cache.expand_to(Vector2i(E->key().x, E->key().y)); + } } - - used_size_cache.size += Vector2(1, 1); - } else { - used_size_cache = Rect2(); } - used_size_cache_dirty = false; + if (!first) { // first is true if every layer is empty. + used_rect_cache.size += Vector2i(1, 1); // The cache expands to top-left coordinate, so we add one full tile. + } + used_rect_cache_dirty = false; } - return used_size_cache; + return used_rect_cache; } // --- Override some methods of the CanvasItem class to pass the changes to the quadrants CanvasItems --- @@ -1566,10 +2759,13 @@ Rect2 TileMap::get_used_rect() { // Not const because of cache void TileMap::set_light_mask(int p_light_mask) { // Occlusion: set light mask. CanvasItem::set_light_mask(p_light_mask); - for (Map<Vector2i, TileMapQuadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { - for (List<RID>::Element *F = E->get().canvas_items.front(); F; F = F->next()) { - RenderingServer::get_singleton()->canvas_item_set_light_mask(F->get(), get_light_mask()); + for (unsigned int layer = 0; layer < layers.size(); layer++) { + for (Map<Vector2i, TileMapQuadrant>::Element *E = layers[layer].quadrant_map.front(); E; E = E->next()) { + for (const RID &ci : E->get().canvas_items) { + RenderingServer::get_singleton()->canvas_item_set_light_mask(ci, get_light_mask()); + } } + _rendering_update_layer(layer); } } @@ -1578,11 +2774,14 @@ void TileMap::set_material(const Ref<Material> &p_material) { CanvasItem::set_material(p_material); // Update material for the whole tilemap. - for (Map<Vector2i, TileMapQuadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { - TileMapQuadrant &q = E->get(); - for (List<RID>::Element *F = q.canvas_items.front(); F; F = F->next()) { - RS::get_singleton()->canvas_item_set_use_parent_material(F->get(), get_use_parent_material() || get_material().is_valid()); + for (unsigned int layer = 0; layer < layers.size(); layer++) { + for (Map<Vector2i, TileMapQuadrant>::Element *E = layers[layer].quadrant_map.front(); E; E = E->next()) { + TileMapQuadrant &q = E->get(); + for (const RID &ci : q.canvas_items) { + RS::get_singleton()->canvas_item_set_use_parent_material(ci, get_use_parent_material() || get_material().is_valid()); + } } + _rendering_update_layer(layer); } } @@ -1591,35 +2790,44 @@ void TileMap::set_use_parent_material(bool p_use_parent_material) { CanvasItem::set_use_parent_material(p_use_parent_material); // Update use_parent_material for the whole tilemap. - for (Map<Vector2i, TileMapQuadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { - TileMapQuadrant &q = E->get(); - for (List<RID>::Element *F = q.canvas_items.front(); F; F = F->next()) { - RS::get_singleton()->canvas_item_set_use_parent_material(F->get(), get_use_parent_material() || get_material().is_valid()); + for (unsigned int layer = 0; layer < layers.size(); layer++) { + for (Map<Vector2i, TileMapQuadrant>::Element *E = layers[layer].quadrant_map.front(); E; E = E->next()) { + TileMapQuadrant &q = E->get(); + for (const RID &ci : q.canvas_items) { + RS::get_singleton()->canvas_item_set_use_parent_material(ci, get_use_parent_material() || get_material().is_valid()); + } } + _rendering_update_layer(layer); } } 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 (Map<Vector2i, TileMapQuadrant>::Element *F = quadrant_map.front(); F; F = F->next()) { - TileMapQuadrant &q = F->get(); - for (List<RID>::Element *E = q.canvas_items.front(); E; E = E->next()) { - RenderingServer::get_singleton()->canvas_item_set_default_texture_filter(E->get(), RS::CanvasItemTextureFilter(p_texture_filter)); - _make_quadrant_dirty(F); + 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 (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); + } } + _rendering_update_layer(layer); } } 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 (Map<Vector2i, TileMapQuadrant>::Element *F = quadrant_map.front(); F; F = F->next()) { - TileMapQuadrant &q = F->get(); - for (List<RID>::Element *E = q.canvas_items.front(); E; E = E->next()) { - RenderingServer::get_singleton()->canvas_item_set_default_texture_repeat(E->get(), RS::CanvasItemTextureRepeat(p_texture_repeat)); - _make_quadrant_dirty(F); + 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 (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); + } } + _rendering_update_layer(layer); } } @@ -1668,50 +2876,79 @@ void TileMap::draw_cells_outline(Control *p_control, Set<Vector2i> p_cells, Colo // Create a set. Vector2i tile_size = tile_set->get_tile_size(); - Vector<Vector2> uvs; + Vector<Vector2> polygon = tile_set->get_tile_shape_polygon(); + TileSet::TileShape shape = tile_set->get_tile_shape(); - if (tile_set->get_tile_shape() == TileSet::TILE_SHAPE_SQUARE) { - uvs.append(Vector2(1.0, 0.0)); - uvs.append(Vector2(1.0, 1.0)); - uvs.append(Vector2(0.0, 1.0)); - uvs.append(Vector2(0.0, 0.0)); - } else { - float overlap = 0.0; - switch (tile_set->get_tile_shape()) { - case TileSet::TILE_SHAPE_ISOMETRIC: - overlap = 0.5; - break; - case TileSet::TILE_SHAPE_HEXAGON: - overlap = 0.25; - break; - case TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE: - overlap = 0.0; - break; - default: - break; - } - uvs.append(Vector2(1.0, overlap)); - uvs.append(Vector2(1.0, 1.0 - overlap)); - uvs.append(Vector2(0.5, 1.0)); - uvs.append(Vector2(0.0, 1.0 - overlap)); - uvs.append(Vector2(0.0, overlap)); - uvs.append(Vector2(0.5, 0.0)); - if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL) { - for (int i = 0; i < uvs.size(); i++) { - uvs.write[i] = Vector2(uvs[i].y, uvs[i].x); + for (Set<Vector2i>::Element *E = p_cells.front(); E; E = E->next()) { + Vector2 center = map_to_world(E->get()); + +#define DRAW_SIDE_IF_NEEDED(side, polygon_index_from, polygon_index_to) \ + if (!p_cells.has(get_neighbor_cell(E->get(), 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); \ + } + + if (shape == TileSet::TILE_SHAPE_SQUARE) { + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_RIGHT_SIDE, 1, 2); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE, 2, 3); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_LEFT_SIDE, 3, 0); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_SIDE, 0, 1); + } else { + if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + if (shape == TileSet::TILE_SHAPE_ISOMETRIC) { + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, 3, 4); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, 2, 3); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE, 0, 1); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE, 5, 0); + } else { + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, 3, 4); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, 2, 3); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_LEFT_SIDE, 1, 2); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE, 0, 1); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE, 5, 0); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_RIGHT_SIDE, 4, 5); + } + } else { + if (shape == TileSet::TILE_SHAPE_ISOMETRIC) { + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, 3, 4); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, 5, 0); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE, 0, 1); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE, 2, 3); + } else { + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, 3, 4); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE, 4, 5); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, 5, 0); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE, 0, 1); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_SIDE, 1, 2); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE, 2, 3); + } } } } +#undef DRAW_SIDE_IF_NEEDED +} - for (Set<Vector2i>::Element *E = p_cells.front(); E; E = E->next()) { - Vector2 top_left = map_to_world(E->get()) - tile_size / 2; - TypedArray<Vector2i> surrounding_tiles = get_surrounding_tiles(E->get()); - for (int i = 0; i < surrounding_tiles.size(); i++) { - if (!p_cells.has(surrounding_tiles[i])) { - p_control->draw_line(p_transform.xform(top_left + uvs[i] * tile_size), p_transform.xform(top_left + uvs[(i + 1) % uvs.size()] * tile_size), p_color); - } +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; + for (int layer = 0; layer < (int)layers.size(); layer++) { + if (layers[layer].y_sort_enabled) { + y_sorted_z_index.insert(layers[layer].z_index); + } + } + + // Check if we have a non-sorted layer in a Z-index with a Y-sorted layer. + for (int layer = 0; layer < (int)layers.size(); layer++) { + if (!layers[layer].y_sort_enabled && y_sorted_z_index.has(layers[layer].z_index)) { + warnings.push_back(TTR("A Y-sorted layer has the same Z-index value as a not Y-sorted layer.\nThis may lead to unwanted behaviors, as a layer that is not Y-sorted will be Y-sorted as a whole with tiles from Y-sorted layers.")); + break; } } + + return warnings; } void TileMap::_bind_methods() { @@ -1721,22 +2958,37 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_quadrant_size", "size"), &TileMap::set_quadrant_size); ClassDB::bind_method(D_METHOD("get_quadrant_size"), &TileMap::get_quadrant_size); - ClassDB::bind_method(D_METHOD("set_collision_visibility_mode", "show_collision"), &TileMap::set_collision_visibility_mode); + ClassDB::bind_method(D_METHOD("get_layers_count"), &TileMap::get_layers_count); + ClassDB::bind_method(D_METHOD("add_layer", "to_position"), &TileMap::add_layer); + ClassDB::bind_method(D_METHOD("move_layer", "layer", "to_position"), &TileMap::move_layer); + ClassDB::bind_method(D_METHOD("remove_layer", "layer"), &TileMap::remove_layer); + ClassDB::bind_method(D_METHOD("set_layer_name", "layer", "name"), &TileMap::set_layer_name); + ClassDB::bind_method(D_METHOD("get_layer_name", "layer"), &TileMap::get_layer_name); + ClassDB::bind_method(D_METHOD("set_layer_enabled", "layer", "enabled"), &TileMap::set_layer_enabled); + ClassDB::bind_method(D_METHOD("is_layer_enabled", "layer"), &TileMap::is_layer_enabled); + ClassDB::bind_method(D_METHOD("set_layer_y_sort_enabled", "layer", "y_sort_enabled"), &TileMap::set_layer_y_sort_enabled); + ClassDB::bind_method(D_METHOD("is_layer_y_sort_enabled", "layer"), &TileMap::is_layer_y_sort_enabled); + ClassDB::bind_method(D_METHOD("set_layer_y_sort_origin", "layer", "y_sort_origin"), &TileMap::set_layer_y_sort_origin); + ClassDB::bind_method(D_METHOD("get_layer_y_sort_origin", "layer"), &TileMap::get_layer_y_sort_origin); + ClassDB::bind_method(D_METHOD("set_layer_z_index", "layer", "z_index"), &TileMap::set_layer_z_index); + ClassDB::bind_method(D_METHOD("get_layer_z_index", "layer"), &TileMap::get_layer_z_index); + + ClassDB::bind_method(D_METHOD("set_collision_visibility_mode", "collision_visibility_mode"), &TileMap::set_collision_visibility_mode); ClassDB::bind_method(D_METHOD("get_collision_visibility_mode"), &TileMap::get_collision_visibility_mode); - ClassDB::bind_method(D_METHOD("set_navigation_visibility_mode", "show_navigation"), &TileMap::set_navigation_visibility_mode); + ClassDB::bind_method(D_METHOD("set_navigation_visibility_mode", "navigation_visibility_mode"), &TileMap::set_navigation_visibility_mode); ClassDB::bind_method(D_METHOD("get_navigation_visibility_mode"), &TileMap::get_navigation_visibility_mode); - ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMap::set_cell, DEFVAL(-1), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetSource::INVALID_TILE_ALTERNATIVE)); - ClassDB::bind_method(D_METHOD("get_cell_source_id", "coords"), &TileMap::get_cell_source_id); - ClassDB::bind_method(D_METHOD("get_cell_atlas_coords", "coords"), &TileMap::get_cell_atlas_coords); - ClassDB::bind_method(D_METHOD("get_cell_alternative_tile", "coords"), &TileMap::get_cell_alternative_tile); + ClassDB::bind_method(D_METHOD("set_cell", "layer", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMap::set_cell, DEFVAL(TileSet::INVALID_SOURCE), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetSource::INVALID_TILE_ALTERNATIVE)); + ClassDB::bind_method(D_METHOD("get_cell_source_id", "layer", "coords", "use_proxies"), &TileMap::get_cell_source_id); + ClassDB::bind_method(D_METHOD("get_cell_atlas_coords", "layer", "coords", "use_proxies"), &TileMap::get_cell_atlas_coords); + ClassDB::bind_method(D_METHOD("get_cell_alternative_tile", "layer", "coords", "use_proxies"), &TileMap::get_cell_alternative_tile); ClassDB::bind_method(D_METHOD("fix_invalid_tiles"), &TileMap::fix_invalid_tiles); ClassDB::bind_method(D_METHOD("get_surrounding_tiles", "coords"), &TileMap::get_surrounding_tiles); ClassDB::bind_method(D_METHOD("clear"), &TileMap::clear); - ClassDB::bind_method(D_METHOD("get_used_cells"), &TileMap::get_used_cells); + ClassDB::bind_method(D_METHOD("get_used_cells", "layer"), &TileMap::get_used_cells); ClassDB::bind_method(D_METHOD("get_used_rect"), &TileMap::get_used_rect); ClassDB::bind_method(D_METHOD("map_to_world", "map_position"), &TileMap::map_to_world); @@ -1744,15 +2996,17 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("get_neighbor_cell", "coords", "neighbor"), &TileMap::get_neighbor_cell); - ClassDB::bind_method(D_METHOD("update_dirty_quadrants"), &TileMap::update_dirty_quadrants); + ClassDB::bind_method(D_METHOD("_update_dirty_quadrants"), &TileMap::_update_dirty_quadrants); - ClassDB::bind_method(D_METHOD("_set_tile_data"), &TileMap::_set_tile_data); - ClassDB::bind_method(D_METHOD("_get_tile_data"), &TileMap::_get_tile_data); + ClassDB::bind_method(D_METHOD("_set_tile_data", "layer"), &TileMap::_set_tile_data); + ClassDB::bind_method(D_METHOD("_get_tile_data", "layer"), &TileMap::_get_tile_data); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tile_set", PROPERTY_HINT_RESOURCE_TYPE, "TileSet"), "set_tileset", "get_tileset"); ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_quadrant_size", PROPERTY_HINT_RANGE, "1,128,1"), "set_quadrant_size", "get_quadrant_size"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "show_collision", PROPERTY_HINT_ENUM, "Default,Force Show,Force Hide"), "set_collision_visibility_mode", "get_collision_visibility_mode"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "show_navigation", PROPERTY_HINT_ENUM, "Default,Force Show,Force Hide"), "set_navigation_visibility_mode", "get_navigation_visibility_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_visibility_mode", PROPERTY_HINT_ENUM, "Default,Force Show,Force Hide"), "set_collision_visibility_mode", "get_collision_visibility_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_visibility_mode", PROPERTY_HINT_ENUM, "Default,Force Show,Force Hide"), "set_navigation_visibility_mode", "get_navigation_visibility_mode"); + + ADD_ARRAY("layers", "layer_"); ADD_PROPERTY_DEFAULT("format", FORMAT_1); @@ -1764,18 +3018,21 @@ void TileMap::_bind_methods() { } void TileMap::_tile_set_changed() { - emit_signal("changed"); - _make_all_quadrants_dirty(true); + emit_signal(SNAME("changed")); + _clear_internals(); + _recreate_internals(); } TileMap::TileMap() { set_notify_transform(true); set_notify_local_transform(false); + + layers.resize(1); } TileMap::~TileMap() { if (tile_set.is_valid()) { tile_set->disconnect("changed", callable_mp(this, &TileMap::_tile_set_changed)); } - _clear_quadrants(); + _clear_internals(); } diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 3001e6b471..3ac50fc7cc 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -31,8 +31,6 @@ #ifndef TILE_MAP_H #define TILE_MAP_H -#include "core/templates/self_list.h" -#include "core/templates/vset.h" #include "scene/2d/node_2d.h" #include "scene/gui/control.h" #include "scene/resources/tile_set.h" @@ -99,7 +97,8 @@ struct TileMapQuadrant { // Dirty list element SelfList<TileMapQuadrant> dirty_list_element; - // Quadrant coords. + // Quadrant layer and coords. + int layer = -1; Vector2i coords; // TileMapCells @@ -126,6 +125,7 @@ struct TileMapQuadrant { Map<Vector2i, String> scenes; void operator=(const TileMapQuadrant &q) { + layer = q.layer; coords = q.coords; debug_canvas_item = q.debug_canvas_item; canvas_items = q.canvas_items; @@ -136,6 +136,7 @@ struct TileMapQuadrant { TileMapQuadrant(const TileMapQuadrant &q) : dirty_list_element(this) { + layer = q.layer; coords = q.coords; debug_canvas_item = q.debug_canvas_item; canvas_items = q.canvas_items; @@ -196,11 +197,13 @@ private: }; mutable DataFormat format = FORMAT_1; // Assume lowest possible format if none is present; + static constexpr float FP_ADJUST = 0.00001; + // Properties. Ref<TileSet> tile_set; int quadrant_size = 16; - VisibilityMode show_collision = VISIBILITY_MODE_DEFAULT; - VisibilityMode show_navigation = VISIBILITY_MODE_DEFAULT; + VisibilityMode collision_visibility_mode = VISIBILITY_MODE_DEFAULT; + VisibilityMode navigation_visibility_mode = VISIBILITY_MODE_DEFAULT; // Updates. bool pending_update = false; @@ -208,28 +211,72 @@ private: // Rect. Rect2 rect_cache; bool rect_cache_dirty = true; - Rect2 used_size_cache; - bool used_size_cache_dirty = true; + Rect2i used_rect_cache; + bool used_rect_cache_dirty = true; + + // TileMap layers. + struct TileMapLayer { + String name; + bool enabled = true; + bool y_sort_enabled = false; + int y_sort_origin = 0; + int z_index = 0; + RID canvas_item; + Map<Vector2i, TileMapCell> tile_map; + Map<Vector2i, TileMapQuadrant> quadrant_map; + SelfList<TileMapQuadrant>::List dirty_quadrant_list; + }; + LocalVector<TileMapLayer> layers; + int selected_layer = -1; + + // 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); - // Map of cells. - Map<Vector2i, TileMapCell> tile_map; + void _make_quadrant_dirty(Map<Vector2i, TileMapQuadrant>::Element *Q); + void _make_all_quadrants_dirty(); + void _queue_update_dirty_quadrants(); - // Quadrants management. - Map<Vector2i, TileMapQuadrant> quadrant_map; - Vector2i _coords_to_quadrant_coords(const Vector2i &p_coords) const; - SelfList<TileMapQuadrant>::List dirty_quadrant_list; + void _update_dirty_quadrants(); + + void _recreate_internals(); - Map<Vector2i, TileMapQuadrant>::Element *_create_quadrant(const Vector2i &p_qk); void _erase_quadrant(Map<Vector2i, TileMapQuadrant>::Element *Q); - void _make_all_quadrants_dirty(bool p_update = true); - void _make_quadrant_dirty(Map<Vector2i, TileMapQuadrant>::Element *Q, bool p_update = true); - void _recreate_quadrants(); - void _clear_quadrants(); + void _clear_layer_internals(int p_layer); + void _clear_internals(); + + // Rect caching. void _recompute_rect_cache(); + // Per-system methods. + bool _rendering_quadrant_order_dirty = false; + void _rendering_notification(int p_what); + void _rendering_update_layer(int p_layer); + void _rendering_cleanup_layer(int p_layer); + void _rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list); + void _rendering_create_quadrant(TileMapQuadrant *p_quadrant); + void _rendering_cleanup_quadrant(TileMapQuadrant *p_quadrant); + void _rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant); + + void _physics_notification(int p_what); + void _physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list); + void _physics_create_quadrant(TileMapQuadrant *p_quadrant); + void _physics_cleanup_quadrant(TileMapQuadrant *p_quadrant); + void _physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant); + + void _navigation_notification(int p_what); + void _navigation_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list); + void _navigation_cleanup_quadrant(TileMapQuadrant *p_quadrant); + void _navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant); + + void _scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list); + void _scenes_cleanup_quadrant(TileMapQuadrant *p_quadrant); + void _scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant); + // Set and get tiles from data arrays. - void _set_tile_data(const Vector<int> &p_data); - Vector<int> _get_tile_data() const; + void _set_tile_data(int p_layer, const Vector<int> &p_data); + Vector<int> _get_tile_data(int p_layer) const; void _tile_set_changed(); @@ -258,27 +305,47 @@ public: void set_quadrant_size(int p_size); int get_quadrant_size() const; + static void draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, Color p_modulation = Color(1.0, 1.0, 1.0, 1.0)); + + // Layers management. + int get_layers_count() const; + void add_layer(int p_to_pos); + void move_layer(int p_layer, int p_to_pos); + void remove_layer(int p_layer); + void set_layer_name(int p_layer, String p_name); + String get_layer_name(int p_layer) const; + void set_layer_enabled(int p_layer, bool p_visible); + bool is_layer_enabled(int p_layer) const; + void set_layer_y_sort_enabled(int p_layer, bool p_enabled); + bool is_layer_y_sort_enabled(int p_layer) const; + void set_layer_y_sort_origin(int p_layer, int p_y_sort_origin); + int get_layer_y_sort_origin(int p_layer) const; + void set_layer_z_index(int p_layer, int p_z_index); + int get_layer_z_index(int p_layer) const; + void set_selected_layer(int p_layer_id); // For editor use. + int get_selected_layer() const; + void set_collision_visibility_mode(VisibilityMode p_show_collision); VisibilityMode get_collision_visibility_mode(); void set_navigation_visibility_mode(VisibilityMode p_show_navigation); VisibilityMode get_navigation_visibility_mode(); - void set_cell(const Vector2i &p_coords, int p_source_id = -1, const Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE); - int get_cell_source_id(const Vector2i &p_coords) const; - Vector2i get_cell_atlas_coords(const Vector2i &p_coords) const; - int get_cell_alternative_tile(const Vector2i &p_coords) const; + void set_cell(int p_layer, const Vector2i &p_coords, int p_source_id = -1, const Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE); + int get_cell_source_id(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const; + Vector2i get_cell_atlas_coords(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const; + int get_cell_alternative_tile(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const; - TileMapPattern *get_pattern(TypedArray<Vector2i> p_coords_array); + TileMapPattern *get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array); Vector2i map_pattern(Vector2i p_position_in_tilemap, Vector2i p_coords_in_pattern, const TileMapPattern *p_pattern); - void set_pattern(Vector2i p_position, const TileMapPattern *p_pattern); + void set_pattern(int p_layer, Vector2i p_position, const TileMapPattern *p_pattern); // Not exposed to users - TileMapCell get_cell(const Vector2i &p_coords) const; - Map<Vector2i, TileMapQuadrant> &get_quadrant_map(); - int get_effective_quadrant_size() const; + 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); + int get_effective_quadrant_size(int p_layer) const; + //--- - void update_dirty_quadrants(); virtual void set_y_sort_enabled(bool p_enable) override; Vector2 map_to_world(const Vector2i &p_pos) const; @@ -287,7 +354,7 @@ public: bool is_existing_neighbor(TileSet::CellNeighbor p_cell_neighbor) const; Vector2i get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeighbor p_cell_neighbor) const; - TypedArray<Vector2i> get_used_cells() const; + TypedArray<Vector2i> get_used_cells(int p_layer) const; Rect2 get_used_rect(); // Not const because of cache // Override some methods of the CanvasItem class to pass the changes to the quadrants CanvasItems @@ -297,13 +364,19 @@ public: virtual void set_texture_filter(CanvasItem::TextureFilter p_texture_filter) override; virtual void set_texture_repeat(CanvasItem::TextureRepeat p_texture_repeat) override; + // Fixing a nclearing methods. void fix_invalid_tiles(); + + void clear_layer(int p_layer); void clear(); // 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()); + // Configuration warnings. + TypedArray<String> get_configuration_warnings() const override; + TileMap(); ~TileMap(); }; diff --git a/scene/2d/touch_screen_button.cpp b/scene/2d/touch_screen_button.cpp index 0a6393551c..8bd7b696f2 100644 --- a/scene/2d/touch_screen_button.cpp +++ b/scene/2d/touch_screen_button.cpp @@ -30,11 +30,8 @@ #include "touch_screen_button.h" -#include "core/input/input.h" -#include "core/input/input_map.h" -#include "core/os/os.h" #include "scene/main/window.h" -# + void TouchScreenButton::set_texture(const Ref<Texture2D> &p_texture) { texture = p_texture; update(); @@ -188,7 +185,7 @@ String TouchScreenButton::get_action() const { return action; } -void TouchScreenButton::_input(const Ref<InputEvent> &p_event) { +void TouchScreenButton::input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (!get_tree()) { @@ -291,10 +288,10 @@ void TouchScreenButton::_press(int p_finger_pressed) { iea.instantiate(); iea->set_action(action); iea->set_pressed(true); - get_viewport()->input(iea, true); + get_viewport()->push_input(iea, true); } - emit_signal("pressed"); + emit_signal(SNAME("pressed")); update(); } @@ -308,12 +305,12 @@ void TouchScreenButton::_release(bool p_exiting_tree) { iea.instantiate(); iea->set_action(action); iea->set_pressed(false); - get_viewport()->input(iea, true); + get_viewport()->push_input(iea, true); } } if (!p_exiting_tree) { - emit_signal("released"); + emit_signal(SNAME("released")); update(); } } @@ -387,8 +384,6 @@ void TouchScreenButton::_bind_methods() { ClassDB::bind_method(D_METHOD("is_pressed"), &TouchScreenButton::is_pressed); - ClassDB::bind_method(D_METHOD("_input"), &TouchScreenButton::_input); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "normal", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "pressed", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture_pressed", "get_texture_pressed"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "bitmask", PROPERTY_HINT_RESOURCE_TYPE, "BitMap"), "set_bitmask", "get_bitmask"); diff --git a/scene/2d/touch_screen_button.h b/scene/2d/touch_screen_button.h index 10820ad059..1c515149d4 100644 --- a/scene/2d/touch_screen_button.h +++ b/scene/2d/touch_screen_button.h @@ -61,7 +61,7 @@ private: VisibilityMode visibility = VISIBILITY_ALWAYS; - void _input(const Ref<InputEvent> &p_event); + virtual void input(const Ref<InputEvent> &p_event) override; bool _is_point_inside(const Point2 &p_point); diff --git a/scene/2d/visible_on_screen_notifier_2d.cpp b/scene/2d/visible_on_screen_notifier_2d.cpp index 25237edacf..eb4bedb6a3 100644 --- a/scene/2d/visible_on_screen_notifier_2d.cpp +++ b/scene/2d/visible_on_screen_notifier_2d.cpp @@ -30,12 +30,6 @@ #include "visible_on_screen_notifier_2d.h" -#include "core/config/engine.h" -#include "gpu_particles_2d.h" -#include "scene/2d/animated_sprite_2d.h" -#include "scene/2d/physics_body_2d.h" -#include "scene/animation/animation_player.h" -#include "scene/main/window.h" #include "scene/scene_string_names.h" #ifdef TOOLS_ENABLED diff --git a/scene/3d/SCsub b/scene/3d/SCsub index 40bdaee47d..fc61250247 100644 --- a/scene/3d/SCsub +++ b/scene/3d/SCsub @@ -2,7 +2,4 @@ Import("env") -if env["disable_3d"]: - env.add_source_files(env.scene_sources, "node_3d.cpp") -else: - env.add_source_files(env.scene_sources, "*.cpp") +env.add_source_files(env.scene_sources, "*.cpp") diff --git a/scene/3d/area_3d.cpp b/scene/3d/area_3d.cpp index cd64a813dd..943586f43c 100644 --- a/scene/3d/area_3d.cpp +++ b/scene/3d/area_3d.cpp @@ -32,7 +32,6 @@ #include "scene/scene_string_names.h" #include "servers/audio_server.h" -#include "servers/physics_server_3d.h" void Area3D::set_space_override_mode(SpaceOverride p_mode) { space_override = p_mode; @@ -106,6 +105,61 @@ real_t Area3D::get_priority() const { return priority; } +void Area3D::set_wind_force_magnitude(real_t p_wind_force_magnitude) { + wind_force_magnitude = p_wind_force_magnitude; + if (is_inside_tree()) { + _initialize_wind(); + } +} + +real_t Area3D::get_wind_force_magnitude() const { + return wind_force_magnitude; +} + +void Area3D::set_wind_attenuation_factor(real_t p_wind_force_attenuation_factor) { + wind_attenuation_factor = p_wind_force_attenuation_factor; + if (is_inside_tree()) { + _initialize_wind(); + } +} + +real_t Area3D::get_wind_attenuation_factor() const { + return wind_attenuation_factor; +} + +void Area3D::set_wind_source_path(const NodePath &p_wind_source_path) { + wind_source_path = p_wind_source_path; + if (is_inside_tree()) { + _initialize_wind(); + } +} + +const NodePath &Area3D::get_wind_source_path() const { + return wind_source_path; +} + +void Area3D::_initialize_wind() { + real_t temp_magnitude = 0.0; + Vector3 wind_direction(0., 0., 0.); + Vector3 wind_source(0., 0., 0.); + + // Overwrite with area-specified info if available + if (!wind_source_path.is_empty()) { + 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_source = global_transform.origin; + temp_magnitude = wind_force_magnitude; + } + + // Set force, source and direction in the physics server. + PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_WIND_ATTENUATION_FACTOR, wind_attenuation_factor); + PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_WIND_SOURCE, wind_source); + PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_WIND_DIRECTION, wind_direction); + PhysicsServer3D::get_singleton()->area_set_param(get_rid(), PhysicsServer3D::AREA_PARAM_WIND_FORCE_MAGNITUDE, temp_magnitude); +} + void Area3D::_body_enter_tree(ObjectID p_id) { Object *obj = ObjectDB::get_instance(p_id); Node *node = Object::cast_to<Node>(obj); @@ -265,6 +319,8 @@ void Area3D::_clear_monitoring() { void Area3D::_notification(int p_what) { if (p_what == NOTIFICATION_EXIT_TREE) { _clear_monitoring(); + } else if (p_what == NOTIFICATION_ENTER_TREE) { + _initialize_wind(); } } @@ -551,6 +607,15 @@ void Area3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_priority", "priority"), &Area3D::set_priority); ClassDB::bind_method(D_METHOD("get_priority"), &Area3D::get_priority); + ClassDB::bind_method(D_METHOD("set_wind_force_magnitude", "wind_force_magnitude"), &Area3D::set_wind_force_magnitude); + ClassDB::bind_method(D_METHOD("get_wind_force_magnitude"), &Area3D::get_wind_force_magnitude); + + ClassDB::bind_method(D_METHOD("set_wind_attenuation_factor", "wind_attenuation_factor"), &Area3D::set_wind_attenuation_factor); + ClassDB::bind_method(D_METHOD("get_wind_attenuation_factor"), &Area3D::get_wind_attenuation_factor); + + ClassDB::bind_method(D_METHOD("set_wind_source_path", "wind_source_path"), &Area3D::set_wind_source_path); + ClassDB::bind_method(D_METHOD("get_wind_source_path"), &Area3D::get_wind_source_path); + ClassDB::bind_method(D_METHOD("set_monitorable", "enable"), &Area3D::set_monitorable); ClassDB::bind_method(D_METHOD("is_monitorable"), &Area3D::is_monitorable); @@ -606,6 +671,9 @@ void Area3D::_bind_methods() { 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, "linear_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_linear_damp", "get_linear_damp"); 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::FLOAT, "wind_force_magnitude", PROPERTY_HINT_RANGE, "0,10,0.001,or_greater"), "set_wind_force_magnitude", "get_wind_force_magnitude"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wind_attenuation_factor", PROPERTY_HINT_RANGE, "0.0,3.0,0.001,or_greater"), "set_wind_attenuation_factor", "get_wind_attenuation_factor"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "wind_source_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_wind_source_path", "get_wind_source_path"); ADD_GROUP("Audio Bus", "audio_bus_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "audio_bus_override"), "set_audio_bus_override", "is_overriding_audio_bus"); diff --git a/scene/3d/area_3d.h b/scene/3d/area_3d.h index 5b8d612717..847d1c5966 100644 --- a/scene/3d/area_3d.h +++ b/scene/3d/area_3d.h @@ -55,6 +55,9 @@ private: real_t angular_damp = 0.1; real_t linear_damp = 0.1; int priority = 0; + real_t wind_force_magnitude = 0.0; + real_t wind_attenuation_factor = 0.0; + NodePath wind_source_path; bool monitoring = false; bool monitorable = false; bool locked = false; @@ -134,6 +137,8 @@ private: void _validate_property(PropertyInfo &property) const override; + void _initialize_wind(); + protected: void _notification(int p_what); static void _bind_methods(); @@ -163,6 +168,15 @@ public: void set_priority(real_t p_priority); real_t get_priority() const; + void set_wind_force_magnitude(real_t p_wind_force_magnitude); + real_t get_wind_force_magnitude() const; + + void set_wind_attenuation_factor(real_t p_wind_attenuation_factor); + real_t get_wind_attenuation_factor() const; + + void set_wind_source_path(const NodePath &p_wind_source_path); + const NodePath &get_wind_source_path() const; + void set_monitoring(bool p_enable); bool is_monitoring() const; diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp index ba559b4ecb..907c6cd03a 100644 --- a/scene/3d/audio_stream_player_3d.cpp +++ b/scene/3d/audio_stream_player_3d.cpp @@ -30,11 +30,10 @@ #include "audio_stream_player_3d.h" -#include "core/config/engine.h" #include "scene/3d/area_3d.h" #include "scene/3d/camera_3d.h" #include "scene/3d/listener_3d.h" -#include "scene/main/window.h" +#include "scene/main/viewport.h" // Based on "A Novel Multichannel Panning Method for Standard and Arbitrary Loudspeaker Configurations" by Ramy Sadek and Chris Kyriakakis (2004) // Speaker-Placement Correction Amplitude Panning (SPCAP) @@ -96,7 +95,7 @@ static const Vector3 speaker_directions[7] = { Vector3(1.0, 0.0, 0.0).normalized(), // side-right }; -void AudioStreamPlayer3D::_calc_output_vol(const Vector3 &source_dir, real_t tightness, AudioStreamPlayer3D::Output &output) { +void AudioStreamPlayer3D::_calc_output_vol(const Vector3 &source_dir, real_t tightness, Vector<AudioFrame> &output) { unsigned int speaker_count = 0; // only main speakers (no LFE) switch (AudioServer::get_singleton()->get_speaker_mode()) { case AudioServer::SPEAKER_MODE_STEREO: @@ -119,182 +118,94 @@ void AudioStreamPlayer3D::_calc_output_vol(const Vector3 &source_dir, real_t tig switch (AudioServer::get_singleton()->get_speaker_mode()) { case AudioServer::SPEAKER_SURROUND_71: - output.vol[3].l = volumes[5]; // side-left - output.vol[3].r = volumes[6]; // side-right + output.write[3].l = volumes[5]; // side-left + output.write[3].r = volumes[6]; // side-right [[fallthrough]]; case AudioServer::SPEAKER_SURROUND_51: - output.vol[2].l = volumes[3]; // rear-left - output.vol[2].r = volumes[4]; // rear-right + output.write[2].l = volumes[3]; // rear-left + output.write[2].r = volumes[4]; // rear-right [[fallthrough]]; case AudioServer::SPEAKER_SURROUND_31: - output.vol[1].r = 1.0; // LFE - always full power - output.vol[1].l = volumes[2]; // center + output.write[1].r = 1.0; // LFE - always full power + output.write[1].l = volumes[2]; // center [[fallthrough]]; case AudioServer::SPEAKER_MODE_STEREO: - output.vol[0].r = volumes[1]; // front-right - output.vol[0].l = volumes[0]; // front-left + output.write[0].r = volumes[1]; // front-right + output.write[0].l = volumes[0]; // front-left break; } } -void AudioStreamPlayer3D::_mix_audio() { - if (!stream_playback.is_valid() || !active.is_set() || - (stream_paused && !stream_paused_fade_out)) { - return; - } - - bool started = false; - if (setseek.get() >= 0.0) { - stream_playback->start(setseek.get()); - setseek.set(-1.0); //reset seek - started = true; - } +void AudioStreamPlayer3D::_calc_reverb_vol(Area3D *area, Vector3 listener_area_pos, Vector<AudioFrame> direct_path_vol, Vector<AudioFrame> &reverb_vol) { + reverb_vol.resize(4); + reverb_vol.write[0] = AudioFrame(0, 0); + reverb_vol.write[1] = AudioFrame(0, 0); + reverb_vol.write[2] = AudioFrame(0, 0); + reverb_vol.write[3] = AudioFrame(0, 0); - //get data - AudioFrame *buffer = mix_buffer.ptrw(); - int buffer_size = mix_buffer.size(); + float uniformity = area->get_reverb_uniformity(); + float area_send = area->get_reverb_amount(); - if (stream_paused_fade_out) { - // Short fadeout ramp - buffer_size = MIN(buffer_size, 128); - } - - // Mix if we're not paused or we're fading out - if ((output_count.get() > 0 || out_of_range_mode == OUT_OF_RANGE_MIX)) { - float output_pitch_scale = 0.0; - if (output_count.get()) { - //used for doppler, not realistic but good enough - for (int i = 0; i < output_count.get(); i++) { - output_pitch_scale += outputs[i].pitch_scale; - } - output_pitch_scale /= float(output_count.get()); - } else { - output_pitch_scale = 1.0; - } + if (uniformity > 0.0) { + float distance = listener_area_pos.length(); + float attenuation = Math::db2linear(_get_attenuation_db(distance)); - stream_playback->mix(buffer, pitch_scale * output_pitch_scale, buffer_size); - } + // Determine the fraction of sound that would come from each speaker if they were all driven uniformly. + float center_val[3] = { 0.5f, 0.25f, 0.16666f }; + int channel_count = AudioServer::get_singleton()->get_channel_count(); + AudioFrame center_frame(center_val[channel_count - 1], center_val[channel_count - 1]); - //write all outputs - for (int i = 0; i < output_count.get(); i++) { - Output current = outputs[i]; + if (attenuation < 1.0) { + //pan the uniform sound + Vector3 rev_pos = listener_area_pos; + rev_pos.y = 0; + rev_pos.normalize(); - //see if current output exists, to keep volume ramp - bool found = false; - for (int j = i; j < prev_output_count; j++) { - if (prev_outputs[j].viewport == current.viewport) { - if (j != i) { - SWAP(prev_outputs[j], prev_outputs[i]); - } - found = true; - break; + if (channel_count >= 1) { + // Stereo pair + float c = rev_pos.x * 0.5 + 0.5; + reverb_vol.write[0].l = 1.0 - c; + reverb_vol.write[0].r = c; } - } - bool interpolate_filter = !started; + if (channel_count >= 3) { + // Center pair + Side pair + float xl = Vector3(-1, 0, -1).normalized().dot(rev_pos) * 0.5 + 0.5; + float xr = Vector3(1, 0, -1).normalized().dot(rev_pos) * 0.5 + 0.5; - if (!found) { - //create new if was not used before - if (prev_output_count < MAX_OUTPUTS) { - prev_outputs[prev_output_count] = prev_outputs[i]; //may be owned by another viewport - prev_output_count++; + reverb_vol.write[1].l = xl; + reverb_vol.write[1].r = xr; + reverb_vol.write[2].l = 1.0 - xr; + reverb_vol.write[2].r = 1.0 - xl; } - prev_outputs[i] = current; - interpolate_filter = false; - } - - //mix! - - int buffers = AudioServer::get_singleton()->get_channel_count(); - - for (int k = 0; k < buffers; k++) { - AudioFrame target_volume = stream_paused_fade_out ? AudioFrame(0.f, 0.f) : current.vol[k]; - AudioFrame vol_prev = stream_paused_fade_in ? AudioFrame(0.f, 0.f) : prev_outputs[i].vol[k]; - AudioFrame vol_inc = (target_volume - vol_prev) / float(buffer_size); - AudioFrame vol = vol_prev; - if (!AudioServer::get_singleton()->thread_has_channel_mix_buffer(current.bus_index, k)) { - continue; //may have been deleted, will be updated on process + if (channel_count >= 4) { + // Rear pair + // FIXME: Not sure what math should be done here + float c = rev_pos.x * 0.5 + 0.5; + reverb_vol.write[3].l = 1.0 - c; + reverb_vol.write[3].r = c; } - AudioFrame *target = AudioServer::get_singleton()->thread_get_channel_mix_buffer(current.bus_index, k); - current.filter.set_mode(AudioFilterSW::HIGHSHELF); - current.filter.set_sampling_rate(AudioServer::get_singleton()->get_mix_rate()); - current.filter.set_cutoff(attenuation_filter_cutoff_hz); - current.filter.set_resonance(1); - current.filter.set_stages(1); - current.filter.set_gain(current.filter_gain); - - if (interpolate_filter) { - current.filter_process[k * 2 + 0] = prev_outputs[i].filter_process[k * 2 + 0]; - current.filter_process[k * 2 + 1] = prev_outputs[i].filter_process[k * 2 + 1]; - - current.filter_process[k * 2 + 0].set_filter(¤t.filter, false); - current.filter_process[k * 2 + 1].set_filter(¤t.filter, false); - - current.filter_process[k * 2 + 0].update_coeffs(buffer_size); - current.filter_process[k * 2 + 1].update_coeffs(buffer_size); - for (int j = 0; j < buffer_size; j++) { - AudioFrame f = buffer[j] * vol; - current.filter_process[k * 2 + 0].process_one_interp(f.l); - current.filter_process[k * 2 + 1].process_one_interp(f.r); - - target[j] += f; - vol += vol_inc; - } - } else { - current.filter_process[k * 2 + 0].set_filter(¤t.filter); - current.filter_process[k * 2 + 1].set_filter(¤t.filter); - - current.filter_process[k * 2 + 0].update_coeffs(); - current.filter_process[k * 2 + 1].update_coeffs(); - for (int j = 0; j < buffer_size; j++) { - AudioFrame f = buffer[j] * vol; - current.filter_process[k * 2 + 0].process_one(f.l); - current.filter_process[k * 2 + 1].process_one(f.r); - - target[j] += f; - vol += vol_inc; - } + for (int i = 0; i < channel_count; i++) { + reverb_vol.write[i] = reverb_vol[i].lerp(center_frame, attenuation); } - - if (current.reverb_bus_index >= 0) { - if (!AudioServer::get_singleton()->thread_has_channel_mix_buffer(current.reverb_bus_index, k)) { - continue; //may have been deleted, will be updated on process - } - - AudioFrame *rtarget = AudioServer::get_singleton()->thread_get_channel_mix_buffer(current.reverb_bus_index, k); - - if (current.reverb_bus_index == prev_outputs[i].reverb_bus_index) { - AudioFrame rvol_inc = (current.reverb_vol[k] - prev_outputs[i].reverb_vol[k]) / float(buffer_size); - AudioFrame rvol = prev_outputs[i].reverb_vol[k]; - - for (int j = 0; j < buffer_size; j++) { - rtarget[j] += buffer[j] * rvol; - rvol += rvol_inc; - } - } else { - AudioFrame rvol = current.reverb_vol[k]; - for (int j = 0; j < buffer_size; j++) { - rtarget[j] += buffer[j] * rvol; - } - } + } else { + for (int i = 0; i < channel_count; i++) { + reverb_vol.write[i] = center_frame; } } - prev_outputs[i] = current; - } - - prev_output_count = output_count.get(); + for (int i = 0; i < channel_count; i++) { + reverb_vol.write[i] = direct_path_vol[i].lerp(reverb_vol[i] * attenuation, uniformity); + reverb_vol.write[i] *= area_send; + } - //stream is no longer active, disable this. - if (!stream_playback->is_playing()) { - active.clear(); + } else { + for (int i = 0; i < 4; i++) { + reverb_vol.write[i] = direct_path_vol[i] * area_send; + } } - - output_ready.clear(); - stream_paused_fade_in = false; - stream_paused_fade_out = false; } float AudioStreamPlayer3D::_get_attenuation_db(float p_distance) const { @@ -330,14 +241,15 @@ float AudioStreamPlayer3D::_get_attenuation_db(float p_distance) const { void AudioStreamPlayer3D::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { velocity_tracker->reset(get_global_transform().origin); - AudioServer::get_singleton()->add_callback(_mix_audios, this); + AudioServer::get_singleton()->add_listener_changed_callback(_listener_changed_cb, this); if (autoplay && !Engine::get_singleton()->is_editor_hint()) { play(); } } if (p_what == NOTIFICATION_EXIT_TREE) { - AudioServer::get_singleton()->remove_callback(_mix_audios, this); + stop(); + AudioServer::get_singleton()->remove_listener_changed_callback(_listener_changed_cb, this); } if (p_what == NOTIFICATION_PAUSED) { @@ -359,281 +271,240 @@ void AudioStreamPlayer3D::_notification(int p_what) { if (p_what == 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())) { + volume_vector = _update_panning(); + } - if (!output_ready.is_set()) { - Vector3 linear_velocity; + if (setplay.get() >= 0 && stream.is_valid()) { + 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; + bus_map[_get_actual_bus()] = volume_vector; + AudioServer::get_singleton()->start_playback_stream(new_playback, bus_map, setplay.get(), linear_attenuation, attenuation_filter_cutoff_hz, actual_pitch_scale); + stream_playbacks.push_back(new_playback); + setplay.set(-1); + } - //compute linear velocity for doppler - if (doppler_tracking != DOPPLER_TRACKING_DISABLED) { - linear_velocity = velocity_tracker->get_tracked_linear_velocity(); + if (!stream_playbacks.is_empty() && active.is_set()) { + // Stop playing if no longer active. + Vector<Ref<AudioStreamPlayback>> playbacks_to_remove; + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (playback.is_valid() && !AudioServer::get_singleton()->is_playback_active(playback) && !AudioServer::get_singleton()->is_playback_paused(playback)) { + emit_signal(SNAME("finished")); + playbacks_to_remove.push_back(playback); + } } + // Now go through and remove playbacks that have finished. Removing elements from a Vector in a range based for is asking for trouble. + for (Ref<AudioStreamPlayback> &playback : playbacks_to_remove) { + stream_playbacks.erase(playback); + } + if (!playbacks_to_remove.is_empty() && stream_playbacks.is_empty()) { + // This node is no longer actively playing audio. + active.clear(); + set_physics_process_internal(false); + } + } - Ref<World3D> world_3d = get_world_3d(); - ERR_FAIL_COND(world_3d.is_null()); - - int new_output_count = 0; + while (stream_playbacks.size() > max_polyphony) { + AudioServer::get_singleton()->stop_playback_stream(stream_playbacks[0]); + stream_playbacks.remove(0); + } + } +} - Vector3 global_pos = get_global_transform().origin; +Area3D *AudioStreamPlayer3D::_get_overriding_area() { + //check if any area is diverting sound into a bus + Ref<World3D> world_3d = get_world_3d(); + ERR_FAIL_COND_V(world_3d.is_null(), nullptr); - int bus_index = AudioServer::get_singleton()->thread_find_bus_index(bus); + Vector3 global_pos = get_global_transform().origin; - //check if any area is diverting sound into a bus + PhysicsDirectSpaceState3D *space_state = PhysicsServer3D::get_singleton()->space_get_direct_state(world_3d->get_space()); - PhysicsDirectSpaceState3D *space_state = PhysicsServer3D::get_singleton()->space_get_direct_state(world_3d->get_space()); + PhysicsDirectSpaceState3D::ShapeResult sr[MAX_INTERSECT_AREAS]; - PhysicsDirectSpaceState3D::ShapeResult sr[MAX_INTERSECT_AREAS]; + int areas = space_state->intersect_point(global_pos, sr, MAX_INTERSECT_AREAS, Set<RID>(), area_mask, false, true); - int areas = space_state->intersect_point(global_pos, sr, MAX_INTERSECT_AREAS, Set<RID>(), area_mask, false, true); - Area3D *area = nullptr; + for (int i = 0; i < areas; i++) { + if (!sr[i].collider) { + continue; + } - for (int i = 0; i < areas; i++) { - if (!sr[i].collider) { - continue; - } + Area3D *tarea = Object::cast_to<Area3D>(sr[i].collider); + if (!tarea) { + continue; + } - Area3D *tarea = Object::cast_to<Area3D>(sr[i].collider); - if (!tarea) { - continue; - } + if (!tarea->is_overriding_audio_bus() && !tarea->is_using_reverb_bus()) { + continue; + } - if (!tarea->is_overriding_audio_bus() && !tarea->is_using_reverb_bus()) { - continue; - } + return tarea; + } + return nullptr; +} - area = tarea; - break; - } +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()) { + return overriding_area->get_audio_bus_name(); + } + return bus; +} - for (const Set<Camera3D *>::Element *E = world_3d->get_cameras().front(); E; E = E->next()) { - Camera3D *camera = E->get(); - Viewport *vp = camera->get_viewport(); - if (!vp->is_audio_listener()) { - continue; - } +Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() { + Vector<AudioFrame> output_volume_vector; + output_volume_vector.resize(4); + for (AudioFrame &frame : output_volume_vector) { + frame = AudioFrame(0, 0); + } - bool listener_is_camera = true; - Node3D *listener_node = camera; + if (!active.is_set() || stream.is_null()) { + return output_volume_vector; + } - Listener3D *listener = vp->get_listener(); - if (listener) { - listener_node = listener; - listener_is_camera = false; - } + Vector3 linear_velocity; - Vector3 local_pos = listener_node->get_global_transform().orthonormalized().affine_inverse().xform(global_pos); + //compute linear velocity for doppler + if (doppler_tracking != DOPPLER_TRACKING_DISABLED) { + linear_velocity = velocity_tracker->get_tracked_linear_velocity(); + } - float dist = local_pos.length(); + Vector3 global_pos = get_global_transform().origin; - Vector3 area_sound_pos; - Vector3 listener_area_pos; + Ref<World3D> world_3d = get_world_3d(); + ERR_FAIL_COND_V(world_3d.is_null(), output_volume_vector); - if (area && area->is_using_reverb_bus() && area->get_reverb_uniformity() > 0) { - area_sound_pos = space_state->get_closest_point_to_object_volume(area->get_rid(), listener_node->get_global_transform().origin); - listener_area_pos = listener_node->get_global_transform().affine_inverse().xform(area_sound_pos); - } + Set<Camera3D *> cameras = world_3d->get_cameras(); + cameras.insert(get_viewport()->get_camera_3d()); - if (max_distance > 0) { - float total_max = max_distance; + PhysicsDirectSpaceState3D *space_state = PhysicsServer3D::get_singleton()->space_get_direct_state(world_3d->get_space()); - if (area && area->is_using_reverb_bus() && area->get_reverb_uniformity() > 0) { - total_max = MAX(total_max, listener_area_pos.length()); - } - if (total_max > max_distance) { - continue; //can't hear this sound in this listener - } - } - - float multiplier = Math::db2linear(_get_attenuation_db(dist)); - if (max_distance > 0) { - multiplier *= MAX(0, 1.0 - (dist / max_distance)); - } + for (Camera3D *camera : cameras) { + Viewport *vp = camera->get_viewport(); + if (!vp->is_audio_listener_3d()) { + continue; + } - Output output; - output.bus_index = bus_index; - output.reverb_bus_index = -1; //no reverb by default - output.viewport = vp; + bool listener_is_camera = true; + Node3D *listener_node = camera; - float db_att = (1.0 - MIN(1.0, multiplier)) * attenuation_filter_db; + Listener3D *listener = vp->get_listener_3d(); + if (listener) { + listener_node = listener; + listener_is_camera = false; + } - 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 angle = Math::rad2deg(Math::acos(c)); - if (angle > emission_angle) { - db_att -= -emission_angle_filter_attenuation_db; - } - } + Vector3 local_pos = listener_node->get_global_transform().orthonormalized().affine_inverse().xform(global_pos); - output.filter_gain = Math::db2linear(db_att); + float dist = local_pos.length(); - //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); + Vector3 area_sound_pos; + Vector3 listener_area_pos; - unsigned int cc = AudioServer::get_singleton()->get_channel_count(); - for (unsigned int k = 0; k < cc; k++) { - output.vol[k] *= multiplier; - } + Area3D *area = _get_overriding_area(); - bool filled_reverb = false; - int vol_index_max = AudioServer::get_singleton()->get_speaker_mode() + 1; - - if (area) { - if (area->is_overriding_audio_bus()) { - //override audio bus - StringName bus_name = area->get_audio_bus_name(); - output.bus_index = AudioServer::get_singleton()->thread_find_bus_index(bus_name); - } - - if (area->is_using_reverb_bus()) { - filled_reverb = true; - StringName bus_name = area->get_reverb_bus(); - output.reverb_bus_index = AudioServer::get_singleton()->thread_find_bus_index(bus_name); - - float uniformity = area->get_reverb_uniformity(); - float area_send = area->get_reverb_amount(); - - if (uniformity > 0.0) { - float distance = listener_area_pos.length(); - float attenuation = Math::db2linear(_get_attenuation_db(distance)); - - //float dist_att_db = -20 * Math::log(dist + 0.00001); //logarithmic attenuation, like in real life - - float center_val[3] = { 0.5f, 0.25f, 0.16666f }; - AudioFrame center_frame(center_val[vol_index_max - 1], center_val[vol_index_max - 1]); - - if (attenuation < 1.0) { - //pan the uniform sound - Vector3 rev_pos = listener_area_pos; - rev_pos.y = 0; - rev_pos.normalize(); - - if (cc >= 1) { - // Stereo pair - float c = rev_pos.x * 0.5 + 0.5; - output.reverb_vol[0].l = 1.0 - c; - output.reverb_vol[0].r = c; - } - - if (cc >= 3) { - // Center pair + Side pair - float xl = Vector3(-1, 0, -1).normalized().dot(rev_pos) * 0.5 + 0.5; - float xr = Vector3(1, 0, -1).normalized().dot(rev_pos) * 0.5 + 0.5; - - output.reverb_vol[1].l = xl; - output.reverb_vol[1].r = xr; - output.reverb_vol[2].l = 1.0 - xr; - output.reverb_vol[2].r = 1.0 - xl; - } - - if (cc >= 4) { - // Rear pair - // FIXME: Not sure what math should be done here - float c = rev_pos.x * 0.5 + 0.5; - output.reverb_vol[3].l = 1.0 - c; - output.reverb_vol[3].r = c; - } - - for (int i = 0; i < vol_index_max; i++) { - output.reverb_vol[i] = output.reverb_vol[i].lerp(center_frame, attenuation); - } - } else { - for (int i = 0; i < vol_index_max; i++) { - output.reverb_vol[i] = center_frame; - } - } - - for (int i = 0; i < vol_index_max; i++) { - output.reverb_vol[i] = output.vol[i].lerp(output.reverb_vol[i] * attenuation, uniformity); - output.reverb_vol[i] *= area_send; - } - - } else { - for (int i = 0; i < vol_index_max; i++) { - output.reverb_vol[i] = output.vol[i] * area_send; - } - } - } - } + if (area && area->is_using_reverb_bus() && area->get_reverb_uniformity() > 0) { + area_sound_pos = space_state->get_closest_point_to_object_volume(area->get_rid(), listener_node->get_global_transform().origin); + listener_area_pos = listener_node->get_global_transform().affine_inverse().xform(area_sound_pos); + } - if (doppler_tracking != DOPPLER_TRACKING_DISABLED) { - Vector3 listener_velocity; + if (max_distance > 0) { + float total_max = max_distance; - if (listener_is_camera) { - listener_velocity = camera->get_doppler_tracked_velocity(); - } + if (area && area->is_using_reverb_bus() && area->get_reverb_uniformity() > 0) { + total_max = MAX(total_max, listener_area_pos.length()); + } + if (total_max > max_distance) { + continue; //can't hear this sound in this listener + } + } - Vector3 local_velocity = listener_node->get_global_transform().orthonormalized().basis.xform_inv(linear_velocity - listener_velocity); + float multiplier = Math::db2linear(_get_attenuation_db(dist)); + if (max_distance > 0) { + multiplier *= MAX(0, 1.0 - (dist / max_distance)); + } - if (local_velocity == Vector3()) { - output.pitch_scale = 1.0; - } else { - float approaching = local_pos.normalized().dot(local_velocity.normalized()); - float velocity = local_velocity.length(); - float speed_of_sound = 343.0; + float db_att = (1.0 - MIN(1.0, multiplier)) * attenuation_filter_db; - output.pitch_scale = speed_of_sound / (speed_of_sound + velocity * approaching); - output.pitch_scale = CLAMP(output.pitch_scale, (1 / 8.0), 8.0); //avoid crazy stuff - } + 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 angle = Math::rad2deg(Math::acos(c)); + if (angle > emission_angle) { + db_att -= -emission_angle_filter_attenuation_db; + } + } - } else { - output.pitch_scale = 1.0; - } + linear_attenuation = Math::db2linear(db_att); + 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); - if (!filled_reverb) { - for (int i = 0; i < vol_index_max; i++) { - output.reverb_vol[i] = AudioFrame(0, 0); - } - } + for (unsigned int k = 0; k < 4; k++) { + output_volume_vector.write[k] = multiplier * output_volume_vector[k]; + } - outputs[new_output_count] = output; - new_output_count++; - if (new_output_count == MAX_OUTPUTS) { - break; - } + Map<StringName, Vector<AudioFrame>> bus_volumes; + if (area) { + if (area->is_overriding_audio_bus()) { + //override audio bus + bus_volumes[area->get_audio_bus_name()] = output_volume_vector; } - output_count.set(new_output_count); - output_ready.set(); - } - - //start playing if requested - if (setplay.get() >= 0.0) { - setseek.set(setplay.get()); - active.set(); - setplay.set(-1); + if (area->is_using_reverb_bus()) { + StringName reverb_bus_name = area->get_reverb_bus(); + Vector<AudioFrame> reverb_vol; + _calc_reverb_vol(area, listener_area_pos, output_volume_vector, reverb_vol); + bus_volumes[reverb_bus_name] = reverb_vol; + } + } else { + bus_volumes[bus] = output_volume_vector; } - //stop playing if no longer active - if (!active.is_set()) { - set_physics_process_internal(false); - emit_signal("finished"); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_bus_volumes_linear(playback, bus_volumes); } - } -} -void AudioStreamPlayer3D::set_stream(Ref<AudioStream> p_stream) { - AudioServer::get_singleton()->lock(); + if (doppler_tracking != DOPPLER_TRACKING_DISABLED) { + Vector3 listener_velocity; - mix_buffer.resize(AudioServer::get_singleton()->thread_get_mix_buffer_size()); + if (listener_is_camera) { + listener_velocity = camera->get_doppler_tracked_velocity(); + } - if (stream_playback.is_valid()) { - stream_playback.unref(); - stream.unref(); - active.clear(); - setseek.set(-1); - } + Vector3 local_velocity = listener_node->get_global_transform().orthonormalized().basis.xform_inv(linear_velocity - listener_velocity); - if (p_stream.is_valid()) { - stream = p_stream; - stream_playback = p_stream->instance_playback(); - } + if (local_velocity != Vector3()) { + float approaching = local_pos.normalized().dot(local_velocity.normalized()); + float velocity = local_velocity.length(); + float speed_of_sound = 343.0; - AudioServer::get_singleton()->unlock(); + float doppler_pitch_scale = pitch_scale * speed_of_sound / (speed_of_sound + velocity * approaching); + doppler_pitch_scale = CLAMP(doppler_pitch_scale, (1 / 8.0), 8.0); //avoid crazy stuff - if (p_stream.is_valid() && stream_playback.is_null()) { - stream.unref(); + actual_pitch_scale = doppler_pitch_scale; + } else { + actual_pitch_scale = pitch_scale; + } + } else { + actual_pitch_scale = pitch_scale; + } + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_pitch_scale(playback, actual_pitch_scale); + } } + return output_volume_vector; +} + +void AudioStreamPlayer3D::set_stream(Ref<AudioStream> p_stream) { + stop(); + stream = p_stream; } Ref<AudioStream> AudioStreamPlayer3D::get_stream() const { @@ -674,49 +545,47 @@ float AudioStreamPlayer3D::get_pitch_scale() const { } void AudioStreamPlayer3D::play(float p_from_pos) { - if (!is_playing()) { - // Reset the prev_output_count if the stream is stopped - prev_output_count = 0; + if (stream.is_null()) { + return; } - - if (stream_playback.is_valid()) { - setplay.set(p_from_pos); - output_ready.clear(); - set_physics_process_internal(true); + ERR_FAIL_COND_MSG(!is_inside_tree(), "Playback can only happen when a node is inside the scene tree"); + if (stream->is_monophonic() && is_playing()) { + stop(); } + setplay.set(p_from_pos); + active.set(); + set_physics_process_internal(true); } void AudioStreamPlayer3D::seek(float p_seconds) { - if (stream_playback.is_valid()) { - setseek.set(p_seconds); - } + stop(); + play(p_seconds); } void AudioStreamPlayer3D::stop() { - if (stream_playback.is_valid()) { - active.clear(); - set_physics_process_internal(false); - setplay.set(-1); + setplay.set(-1); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->stop_playback_stream(playback); } + stream_playbacks.clear(); + active.clear(); + set_physics_process_internal(false); } bool AudioStreamPlayer3D::is_playing() const { - if (stream_playback.is_valid()) { - return active.is_set() || setplay.get() >= 0; + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (AudioServer::get_singleton()->is_playback_active(playback)) { + return true; + } } - return false; } float AudioStreamPlayer3D::get_playback_position() { - if (stream_playback.is_valid()) { - float ss = setseek.get(); - if (ss >= 0.0) { - return ss; - } - return stream_playback->get_playback_position(); + // Return the playback position of the most recently started playback stream. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->get_playback_position(stream_playbacks[stream_playbacks.size() - 1]); } - return 0; } @@ -733,7 +602,7 @@ StringName AudioStreamPlayer3D::get_bus() const { return bus; } } - return "Master"; + return SNAME("Master"); } void AudioStreamPlayer3D::set_autoplay(bool p_enable) { @@ -794,7 +663,7 @@ uint32_t AudioStreamPlayer3D::get_area_mask() const { void AudioStreamPlayer3D::set_emission_angle_enabled(bool p_enable) { emission_angle_enabled = p_enable; - update_gizmo(); + update_gizmos(); } bool AudioStreamPlayer3D::is_emission_angle_enabled() const { @@ -804,7 +673,7 @@ bool AudioStreamPlayer3D::is_emission_angle_enabled() const { void AudioStreamPlayer3D::set_emission_angle(float p_angle) { ERR_FAIL_COND(p_angle < 0 || p_angle > 90); emission_angle = p_angle; - update_gizmo(); + update_gizmos(); } float AudioStreamPlayer3D::get_emission_angle() const { @@ -876,19 +745,35 @@ AudioStreamPlayer3D::DopplerTracking AudioStreamPlayer3D::get_doppler_tracking() } void AudioStreamPlayer3D::set_stream_paused(bool p_pause) { - if (p_pause != stream_paused) { - stream_paused = p_pause; - stream_paused_fade_in = !stream_paused; - stream_paused_fade_out = stream_paused; + // TODO this does not have perfect recall, fix that maybe? If there are zero playbacks registered with the AudioServer, this bool isn't persisted. + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_paused(playback, p_pause); } } bool AudioStreamPlayer3D::get_stream_paused() const { - return stream_paused; + // There's currently no way to pause some playback streams but not others. Check the first and don't bother looking at the rest. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->is_playback_paused(stream_playbacks[0]); + } + return false; } Ref<AudioStreamPlayback> AudioStreamPlayer3D::get_stream_playback() { - return stream_playback; + if (!stream_playbacks.is_empty()) { + return stream_playbacks[stream_playbacks.size() - 1]; + } + return nullptr; +} + +void AudioStreamPlayer3D::set_max_polyphony(int p_max_polyphony) { + if (p_max_polyphony > 0) { + max_polyphony = p_max_polyphony; + } +} + +int AudioStreamPlayer3D::get_max_polyphony() const { + return max_polyphony; } void AudioStreamPlayer3D::_bind_methods() { @@ -956,6 +841,9 @@ void AudioStreamPlayer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer3D::set_stream_paused); ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer3D::get_stream_paused); + 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("get_stream_playback"), &AudioStreamPlayer3D::get_stream_playback); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream"); @@ -969,6 +857,7 @@ void AudioStreamPlayer3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "0,4096,1,or_greater,exp"), "set_max_distance", "get_max_distance"); ADD_PROPERTY(PropertyInfo(Variant::INT, "out_of_range_mode", PROPERTY_HINT_ENUM, "Mix,Pause"), "set_out_of_range_mode", "get_out_of_range_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_polyphony", PROPERTY_HINT_NONE, ""), "set_max_polyphony", "get_max_polyphony"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus"); ADD_PROPERTY(PropertyInfo(Variant::INT, "area_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_area_mask", "get_area_mask"); ADD_GROUP("Emission Angle", "emission_angle"); diff --git a/scene/3d/audio_stream_player_3d.h b/scene/3d/audio_stream_player_3d.h index 8aec493602..abd5a841b2 100644 --- a/scene/3d/audio_stream_player_3d.h +++ b/scene/3d/audio_stream_player_3d.h @@ -31,7 +31,8 @@ #ifndef AUDIO_STREAM_PLAYER_3D_H #define AUDIO_STREAM_PLAYER_3D_H -#include "core/templates/safe_refcount.h" +#include "core/os/mutex.h" +#include "scene/3d/area_3d.h" #include "scene/3d/node_3d.h" #include "scene/3d/velocity_tracker_3d.h" #include "servers/audio/audio_filter_sw.h" @@ -68,32 +69,10 @@ private: }; - struct Output { - AudioFilterSW filter; - AudioFilterSW::Processor filter_process[8]; - AudioFrame vol[4]; - float filter_gain = 0.0; - float pitch_scale = 0.0; - int bus_index = -1; - int reverb_bus_index = -1; - AudioFrame reverb_vol[4]; - Viewport *viewport = nullptr; //pointer only used for reference to previous mix - }; - - Output outputs[MAX_OUTPUTS]; - SafeNumeric<int> output_count; - SafeFlag output_ready; - - //these are used by audio thread to have a reference of previous volumes (for ramping volume and avoiding clicks) - Output prev_outputs[MAX_OUTPUTS]; - int prev_output_count = 0; - - Ref<AudioStreamPlayback> stream_playback; + Vector<Ref<AudioStreamPlayback>> stream_playbacks; Ref<AudioStream> stream; - Vector<AudioFrame> mix_buffer; - SafeNumeric<float> setseek{ -1.0 }; - SafeFlag active; + SafeFlag active{ false }; SafeNumeric<float> setplay{ -1.0 }; AttenuationModel attenuation_model = ATTENUATION_INVERSE_DISTANCE; @@ -101,18 +80,25 @@ private: float unit_size = 10.0; float max_db = 3.0; float pitch_scale = 1.0; + // Internally used to take doppler tracking into account. + float actual_pitch_scale = 1.0; bool autoplay = false; - bool stream_paused = false; - bool stream_paused_fade_in = false; - bool stream_paused_fade_out = false; - StringName bus; + StringName bus = SNAME("Master"); + int max_polyphony = 1; + + uint64_t last_mix_count = -1; - static void _calc_output_vol(const Vector3 &source_dir, real_t tightness, Output &output); - void _mix_audio(); - static void _mix_audios(void *self) { reinterpret_cast<AudioStreamPlayer3D *>(self)->_mix_audio(); } + 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(); } void _set_playing(bool p_enable); bool _is_active() const; + StringName _get_actual_bus(); + Area3D *_get_overriding_area(); + Vector<AudioFrame> _update_panning(); void _bus_layout_changed(); @@ -124,6 +110,8 @@ private: float attenuation_filter_cutoff_hz = 5000.0; float attenuation_filter_db = -24.0; + float linear_attenuation = 0; + float max_distance = 0.0; Ref<VelocityTracker3D> velocity_tracker; @@ -164,6 +152,9 @@ public: void set_bus(const StringName &p_bus); StringName get_bus() const; + void set_max_polyphony(int p_max_polyphony); + int get_max_polyphony() const; + void set_autoplay(bool p_enable); bool is_autoplay_enabled(); diff --git a/scene/3d/bone_attachment_3d.cpp b/scene/3d/bone_attachment_3d.cpp index 5315e685a0..70361f4787 100644 --- a/scene/3d/bone_attachment_3d.cpp +++ b/scene/3d/bone_attachment_3d.cpp @@ -32,7 +32,15 @@ void BoneAttachment3D::_validate_property(PropertyInfo &property) const { if (property.name == "bone_name") { - Skeleton3D *parent = Object::cast_to<Skeleton3D>(get_parent()); + // Because it is a constant function, we cannot use the _get_skeleton_3d function. + 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) { String names; @@ -52,55 +60,321 @@ void BoneAttachment3D::_validate_property(PropertyInfo &property) const { } } +bool BoneAttachment3D::_set(const StringName &p_path, const Variant &p_value) { + if (p_path == SNAME("override_pose")) { + set_override_pose(p_value); + } else if (p_path == SNAME("override_mode")) { + set_override_mode(p_value); + } else if (p_path == SNAME("use_external_skeleton")) { + set_use_external_skeleton(p_value); + } else if (p_path == SNAME("external_skeleton")) { + set_external_skeleton(p_value); + } + + return true; +} + +bool BoneAttachment3D::_get(const StringName &p_path, Variant &r_ret) const { + if (p_path == SNAME("override_pose")) { + r_ret = get_override_pose(); + } else if (p_path == SNAME("override_mode")) { + r_ret = get_override_mode(); + } else if (p_path == SNAME("use_external_skeleton")) { + r_ret = get_use_external_skeleton(); + } else if (p_path == SNAME("external_skeleton")) { + r_ret = get_external_skeleton(); + } + + return true; +} + +void BoneAttachment3D::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back(PropertyInfo(Variant::BOOL, "override_pose", PROPERTY_HINT_NONE, "")); + if (override_pose) { + p_list->push_back(PropertyInfo(Variant::INT, "override_mode", PROPERTY_HINT_ENUM, "Global Pose Override, Local Pose Override, Custom Pose")); + } + + p_list->push_back(PropertyInfo(Variant::BOOL, "use_external_skeleton", PROPERTY_HINT_NONE, "")); + if (use_external_skeleton) { + p_list->push_back(PropertyInfo(Variant::NODE_PATH, "external_skeleton", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton3D")); + } +} + +TypedArray<String> BoneAttachment3D::get_configuration_warnings() const { + TypedArray<String> warnings = Node3D::get_configuration_warnings(); + + if (use_external_skeleton) { + if (external_skeleton_node_cache.is_null()) { + warnings.append(TTR("External Skeleton3D node not set! Please set a path to an external Skeleton3D node.")); + } + } else { + Skeleton3D *parent = Object::cast_to<Skeleton3D>(get_parent()); + if (!parent) { + warnings.append(TTR("Parent node is not a Skeleton3D node! Please use an extenral Skeleton3D if you intend to use the BoneAttachment3D without it being a child of a Skeleton3D node.")); + } + } + + if (bone_idx == -1) { + warnings.append(TTR("BoneAttachment3D node is not bound to any bones! Please select a bone to attach this node.")); + } + + return warnings; +} + +void BoneAttachment3D::_update_external_skeleton_cache() { + external_skeleton_node_cache = ObjectID(); + if (has_node(external_skeleton_node)) { + Node *node = get_node(external_skeleton_node); + ERR_FAIL_COND_MSG(!node, "Cannot update external skeleton cache: Node cannot be found!"); + + // Make sure it's a skeleton3D + Skeleton3D *sk = Object::cast_to<Skeleton3D>(node); + ERR_FAIL_COND_MSG(!sk, "Cannot update external skeleton cache: Skeleton3D Nodepath does not point to a Skeleton3D node!"); + + external_skeleton_node_cache = node->get_instance_id(); + } else { + if (external_skeleton_node.is_empty()) { + BoneAttachment3D *parent_attachment = Object::cast_to<BoneAttachment3D>(get_parent()); + if (parent_attachment) { + parent_attachment->_update_external_skeleton_cache(); + if (parent_attachment->has_node(parent_attachment->external_skeleton_node)) { + Node *node = parent_attachment->get_node(parent_attachment->external_skeleton_node); + ERR_FAIL_COND_MSG(!node, "Cannot update external skeleton cache: Parent's Skeleton3D node cannot be found!"); + + // Make sure it's a skeleton3D + Skeleton3D *sk = Object::cast_to<Skeleton3D>(node); + ERR_FAIL_COND_MSG(!sk, "Cannot update external skeleton cache: Parent Skeleton3D Nodepath does not point to a Skeleton3D node!"); + + external_skeleton_node_cache = node->get_instance_id(); + external_skeleton_node = get_path_to(node); + } + } + } + } +} + void BoneAttachment3D::_check_bind() { - Skeleton3D *sk = Object::cast_to<Skeleton3D>(get_parent()); - if (sk) { - int idx = sk->find_bone(bone_name); - if (idx != -1) { - sk->bind_child_node_to_bone(idx, this); - set_transform(sk->get_bone_global_pose(idx)); + Skeleton3D *sk = _get_skeleton3d(); + + if (sk && !bound) { + if (bone_idx <= -1) { + bone_idx = sk->find_bone(bone_name); + } + if (bone_idx != -1) { + sk->call_deferred("connect", "bone_pose_changed", callable_mp(this, &BoneAttachment3D::on_bone_pose_update)); bound = true; + call_deferred(SNAME("on_bone_pose_update"), bone_idx); + } + } +} + +Skeleton3D *BoneAttachment3D::_get_skeleton3d() { + if (use_external_skeleton) { + if (external_skeleton_node_cache.is_valid()) { + return Object::cast_to<Skeleton3D>(ObjectDB::get_instance(external_skeleton_node_cache)); + } else { + _update_external_skeleton_cache(); + if (external_skeleton_node_cache.is_valid()) { + return Object::cast_to<Skeleton3D>(ObjectDB::get_instance(external_skeleton_node_cache)); + } } + } else { + return Object::cast_to<Skeleton3D>(get_parent()); } + return nullptr; } void BoneAttachment3D::_check_unbind() { if (bound) { - Skeleton3D *sk = Object::cast_to<Skeleton3D>(get_parent()); + Skeleton3D *sk = _get_skeleton3d(); + if (sk) { - int idx = sk->find_bone(bone_name); - if (idx != -1) { - sk->unbind_child_node_from_bone(idx, this); - } + sk->disconnect(SNAME("bone_pose_changed"), callable_mp(this, &BoneAttachment3D::on_bone_pose_update)); } bound = false; } } +void BoneAttachment3D::_transform_changed() { + if (!is_inside_tree()) { + return; + } + + if (override_pose) { + Skeleton3D *sk = _get_skeleton3d(); + + ERR_FAIL_COND_MSG(!sk, "Cannot override pose: Skeleton not found!"); + ERR_FAIL_INDEX_MSG(bone_idx, sk->get_bone_count(), "Cannot override pose: Bone index is out of range!"); + + Transform3D our_trans = get_transform(); + if (use_external_skeleton) { + our_trans = sk->world_transform_to_global_pose(get_global_transform()); + } + + if (override_mode == OVERRIDE_MODES::MODE_GLOBAL_POSE) { + sk->set_bone_global_pose_override(bone_idx, our_trans, 1.0, true); + } else if (override_mode == OVERRIDE_MODES::MODE_LOCAL_POSE) { + sk->set_bone_local_pose_override(bone_idx, sk->global_pose_to_local_pose(bone_idx, our_trans), 1.0, true); + } else if (override_mode == OVERRIDE_MODES::MODE_CUSTOM_POSE) { + sk->set_bone_custom_pose(bone_idx, sk->global_pose_to_local_pose(bone_idx, our_trans)); + } + } +} + void BoneAttachment3D::set_bone_name(const String &p_name) { + bone_name = p_name; + Skeleton3D *sk = _get_skeleton3d(); + if (sk) { + set_bone_idx(sk->find_bone(bone_name)); + } +} + +String BoneAttachment3D::get_bone_name() const { + return bone_name; +} + +void BoneAttachment3D::set_bone_idx(const int &p_idx) { if (is_inside_tree()) { _check_unbind(); } - bone_name = p_name; + bone_idx = p_idx; + + Skeleton3D *sk = _get_skeleton3d(); + if (sk) { + if (bone_idx <= -1 || bone_idx >= sk->get_bone_count()) { + WARN_PRINT("Bone index out of range! Cannot connect BoneAttachment to node!"); + bone_idx = -1; + } else { + bone_name = sk->get_bone_name(bone_idx); + } + } if (is_inside_tree()) { _check_bind(); } + + notify_property_list_changed(); } -String BoneAttachment3D::get_bone_name() const { - return bone_name; +int BoneAttachment3D::get_bone_idx() const { + return bone_idx; +} + +void BoneAttachment3D::set_override_pose(bool p_override) { + override_pose = p_override; + set_notify_local_transform(override_pose); + set_process_internal(override_pose); + + if (!override_pose) { + Skeleton3D *sk = _get_skeleton3d(); + if (sk) { + if (override_mode == OVERRIDE_MODES::MODE_GLOBAL_POSE) { + sk->set_bone_global_pose_override(bone_idx, Transform3D(), 0.0, false); + } else if (override_mode == OVERRIDE_MODES::MODE_LOCAL_POSE) { + sk->set_bone_local_pose_override(bone_idx, Transform3D(), 0.0, false); + } else if (override_mode == OVERRIDE_MODES::MODE_CUSTOM_POSE) { + sk->set_bone_custom_pose(bone_idx, Transform3D()); + } + } + _transform_changed(); + } + notify_property_list_changed(); +} + +bool BoneAttachment3D::get_override_pose() const { + return override_pose; +} + +void BoneAttachment3D::set_override_mode(int p_mode) { + if (override_pose) { + Skeleton3D *sk = _get_skeleton3d(); + if (sk) { + if (override_mode == OVERRIDE_MODES::MODE_GLOBAL_POSE) { + sk->set_bone_global_pose_override(bone_idx, Transform3D(), 0.0, false); + } else if (override_mode == OVERRIDE_MODES::MODE_LOCAL_POSE) { + sk->set_bone_local_pose_override(bone_idx, Transform3D(), 0.0, false); + } else if (override_mode == OVERRIDE_MODES::MODE_CUSTOM_POSE) { + sk->set_bone_custom_pose(bone_idx, Transform3D()); + } + } + override_mode = p_mode; + _transform_changed(); + return; + } + override_mode = p_mode; +} + +int BoneAttachment3D::get_override_mode() const { + return override_mode; +} + +void BoneAttachment3D::set_use_external_skeleton(bool p_use_external) { + use_external_skeleton = p_use_external; + + if (use_external_skeleton) { + _check_unbind(); + _update_external_skeleton_cache(); + _check_bind(); + _transform_changed(); + } + + notify_property_list_changed(); +} + +bool BoneAttachment3D::get_use_external_skeleton() const { + return use_external_skeleton; +} + +void BoneAttachment3D::set_external_skeleton(NodePath p_path) { + external_skeleton_node = p_path; + _update_external_skeleton_cache(); + notify_property_list_changed(); +} + +NodePath BoneAttachment3D::get_external_skeleton() const { + return external_skeleton_node; } void BoneAttachment3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { + if (use_external_skeleton) { + _update_external_skeleton_cache(); + } _check_bind(); } break; case NOTIFICATION_EXIT_TREE: { _check_unbind(); } break; + case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { + _transform_changed(); + } break; + case NOTIFICATION_INTERNAL_PROCESS: { + if (_override_dirty) { + _override_dirty = false; + } + } + } +} + +void BoneAttachment3D::on_bone_pose_update(int p_bone_index) { + if (bone_idx == p_bone_index) { + Skeleton3D *sk = _get_skeleton3d(); + if (sk) { + if (!override_pose) { + if (use_external_skeleton) { + set_global_transform(sk->global_pose_to_world_transform(sk->get_bone_global_pose(bone_idx))); + } else { + set_transform(sk->get_bone_global_pose(bone_idx)); + } + } else { + if (!_override_dirty) { + _transform_changed(); + _override_dirty = true; + } + } + } } } @@ -111,5 +385,21 @@ void BoneAttachment3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_bone_name", "bone_name"), &BoneAttachment3D::set_bone_name); ClassDB::bind_method(D_METHOD("get_bone_name"), &BoneAttachment3D::get_bone_name); + ClassDB::bind_method(D_METHOD("set_bone_idx", "bone_idx"), &BoneAttachment3D::set_bone_idx); + ClassDB::bind_method(D_METHOD("get_bone_idx"), &BoneAttachment3D::get_bone_idx); + + ClassDB::bind_method(D_METHOD("on_bone_pose_update", "bone_index"), &BoneAttachment3D::on_bone_pose_update); + + ClassDB::bind_method(D_METHOD("set_override_pose", "override_pose"), &BoneAttachment3D::set_override_pose); + ClassDB::bind_method(D_METHOD("get_override_pose"), &BoneAttachment3D::get_override_pose); + ClassDB::bind_method(D_METHOD("set_override_mode", "override_mode"), &BoneAttachment3D::set_override_mode); + ClassDB::bind_method(D_METHOD("get_override_mode"), &BoneAttachment3D::get_override_mode); + + ClassDB::bind_method(D_METHOD("set_use_external_skeleton", "use_external_skeleton"), &BoneAttachment3D::set_use_external_skeleton); + 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); + 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 0c6d5f12b1..cf681cace8 100644 --- a/scene/3d/bone_attachment_3d.h +++ b/scene/3d/bone_attachment_3d.h @@ -38,20 +38,59 @@ class BoneAttachment3D : public Node3D { bool bound = false; String bone_name; + int bone_idx = -1; + + bool override_pose = false; + int override_mode = 0; + bool _override_dirty = false; + + enum OVERRIDE_MODES { + MODE_GLOBAL_POSE, + MODE_LOCAL_POSE, + MODE_CUSTOM_POSE + }; + + bool use_external_skeleton = false; + NodePath external_skeleton_node; + ObjectID external_skeleton_node_cache; void _check_bind(); void _check_unbind(); + void _transform_changed(); + void _update_external_skeleton_cache(); + Skeleton3D *_get_skeleton3d(); + protected: virtual void _validate_property(PropertyInfo &property) const override; + bool _get(const StringName &p_path, Variant &r_ret) const; + bool _set(const StringName &p_path, const Variant &p_value); + void _get_property_list(List<PropertyInfo> *p_list) const; void _notification(int p_what); static void _bind_methods(); public: + virtual TypedArray<String> get_configuration_warnings() const override; + void set_bone_name(const String &p_name); String get_bone_name() const; + void set_bone_idx(const int &p_idx); + int get_bone_idx() const; + + void set_override_pose(bool p_override); + bool get_override_pose() const; + void set_override_mode(int p_mode); + int get_override_mode() const; + + void set_use_external_skeleton(bool p_external_skeleton); + bool get_use_external_skeleton() const; + void set_external_skeleton(NodePath p_skeleton); + NodePath get_external_skeleton() const; + + virtual void on_bone_pose_update(int p_bone_index); + BoneAttachment3D(); }; diff --git a/scene/3d/camera_3d.cpp b/scene/3d/camera_3d.cpp index 1ed8c0b4eb..3ada9072c2 100644 --- a/scene/3d/camera_3d.cpp +++ b/scene/3d/camera_3d.cpp @@ -31,10 +31,8 @@ #include "camera_3d.h" #include "collision_object_3d.h" -#include "core/config/engine.h" #include "core/math/camera_matrix.h" -#include "scene/resources/material.h" -#include "scene/resources/surface_tool.h" +#include "scene/main/viewport.h" void Camera3D::_update_audio_listener_state() { } @@ -85,14 +83,14 @@ void Camera3D::_update_camera() { // here goes listener stuff /* if (viewport_ptr && is_inside_scene() && is_current()) - get_viewport()->_camera_transform_changed_notify(); + get_viewport()->_camera_3d_transform_changed_notify(); */ if (get_tree()->is_node_being_edited(this) || !is_current()) { return; } - get_viewport()->_camera_transform_changed_notify(); + get_viewport()->_camera_3d_transform_changed_notify(); } void Camera3D::_notification(int p_what) { @@ -104,9 +102,9 @@ void Camera3D::_notification(int p_what) { viewport = get_viewport(); ERR_FAIL_COND(!viewport); - bool first_camera = viewport->_camera_add(this); + bool first_camera = viewport->_camera_3d_add(this); if (current || first_camera) { - viewport->_camera_set(this); + viewport->_camera_3d_set(this); } } break; @@ -128,7 +126,7 @@ void Camera3D::_notification(int p_what) { } if (viewport) { - viewport->_camera_remove(this); + viewport->_camera_3d_remove(this); viewport = nullptr; } @@ -153,7 +151,7 @@ Transform3D Camera3D::get_camera_transform() const { return tr; } -void Camera3D::set_perspective(float p_fovy_degrees, float p_z_near, float p_z_far) { +void Camera3D::set_perspective(real_t p_fovy_degrees, real_t p_z_near, real_t p_z_far) { if (!force_change && fov == p_fovy_degrees && p_z_near == near && p_z_far == far && mode == PROJECTION_PERSPECTIVE) { return; } @@ -164,11 +162,11 @@ void Camera3D::set_perspective(float p_fovy_degrees, float p_z_near, float p_z_f mode = PROJECTION_PERSPECTIVE; RenderingServer::get_singleton()->camera_set_perspective(camera, fov, near, far); - update_gizmo(); + update_gizmos(); force_change = false; } -void Camera3D::set_orthogonal(float p_size, float p_z_near, float p_z_far) { +void Camera3D::set_orthogonal(real_t p_size, real_t p_z_near, real_t p_z_far) { if (!force_change && size == p_size && p_z_near == near && p_z_far == far && mode == PROJECTION_ORTHOGONAL) { return; } @@ -181,10 +179,10 @@ void Camera3D::set_orthogonal(float p_size, float p_z_near, float p_z_far) { force_change = false; RenderingServer::get_singleton()->camera_set_orthogonal(camera, size, near, far); - update_gizmo(); + update_gizmos(); } -void Camera3D::set_frustum(float p_size, Vector2 p_offset, float p_z_near, float p_z_far) { +void Camera3D::set_frustum(real_t p_size, Vector2 p_offset, real_t p_z_near, real_t p_z_far) { if (!force_change && size == p_size && frustum_offset == p_offset && p_z_near == near && p_z_far == far && mode == PROJECTION_FRUSTUM) { return; } @@ -198,7 +196,7 @@ void Camera3D::set_frustum(float p_size, Vector2 p_offset, float p_z_near, float force_change = false; RenderingServer::get_singleton()->camera_set_frustum(camera, size, frustum_offset, near, far); - update_gizmo(); + update_gizmos(); } void Camera3D::set_projection(Camera3D::Projection p_mode) { @@ -220,7 +218,7 @@ void Camera3D::make_current() { return; } - get_viewport()->_camera_set(this); + get_viewport()->_camera_3d_set(this); //get_scene()->call_group(SceneMainLoop::GROUP_CALL_REALTIME,camera_group,"_camera_make_current",this); } @@ -231,11 +229,11 @@ void Camera3D::clear_current(bool p_enable_next) { return; } - if (get_viewport()->get_camera() == this) { - get_viewport()->_camera_set(nullptr); + if (get_viewport()->get_camera_3d() == this) { + get_viewport()->_camera_3d_set(nullptr); if (p_enable_next) { - get_viewport()->_camera_make_next_current(this); + get_viewport()->_camera_3d_make_next_current(this); } } } @@ -250,7 +248,7 @@ void Camera3D::set_current(bool p_current) { bool Camera3D::is_current() const { if (is_inside_tree() && !get_tree()->is_node_being_edited(this)) { - return get_viewport()->get_camera() == this; + return get_viewport()->get_camera_3d() == this; } else { return current; } @@ -295,7 +293,7 @@ Vector3 Camera3D::project_ray_origin(const Point2 &p_pos) const { return get_camera_transform().origin; } else { Vector2 pos = cpos / viewport_size; - float vsize, hsize; + real_t vsize, hsize; if (keep_aspect == KEEP_WIDTH) { vsize = size / viewport_size.aspect(); hsize = size; @@ -368,7 +366,7 @@ Point2 Camera3D::unproject_position(const Vector3 &p_pos) const { return res; } -Vector3 Camera3D::project_position(const Point2 &p_point, float p_z_depth) const { +Vector3 Camera3D::project_position(const Point2 &p_point, real_t p_z_depth) const { ERR_FAIL_COND_V_MSG(!is_inside_tree(), Vector3(), "Camera is not inside scene."); if (p_z_depth == 0 && mode != PROJECTION_ORTHOGONAL) { @@ -499,8 +497,8 @@ void Camera3D::_bind_methods() { ClassDB::bind_method(D_METHOD("is_position_in_frustum", "world_point"), &Camera3D::is_position_in_frustum); ClassDB::bind_method(D_METHOD("get_camera_rid"), &Camera3D::get_camera); - ClassDB::bind_method(D_METHOD("set_cull_mask_bit", "layer", "enable"), &Camera3D::set_cull_mask_bit); - ClassDB::bind_method(D_METHOD("get_cull_mask_bit", "layer"), &Camera3D::get_cull_mask_bit); + ClassDB::bind_method(D_METHOD("set_cull_mask_value", "layer_number", "value"), &Camera3D::set_cull_mask_value); + ClassDB::bind_method(D_METHOD("get_cull_mask_value", "layer_number"), &Camera3D::get_cull_mask_value); //ClassDB::bind_method(D_METHOD("_camera_make_current"),&Camera::_camera_make_current ); @@ -531,15 +529,15 @@ void Camera3D::_bind_methods() { BIND_ENUM_CONSTANT(DOPPLER_TRACKING_PHYSICS_STEP); } -float Camera3D::get_fov() const { +real_t Camera3D::get_fov() const { return fov; } -float Camera3D::get_size() const { +real_t Camera3D::get_size() const { return size; } -float Camera3D::get_near() const { +real_t Camera3D::get_near() const { return near; } @@ -547,7 +545,7 @@ Vector2 Camera3D::get_frustum_offset() const { return frustum_offset; } -float Camera3D::get_far() const { +real_t Camera3D::get_far() const { return far; } @@ -555,19 +553,19 @@ Camera3D::Projection Camera3D::get_projection() const { return mode; } -void Camera3D::set_fov(float p_fov) { +void Camera3D::set_fov(real_t p_fov) { ERR_FAIL_COND(p_fov < 1 || p_fov > 179); fov = p_fov; _update_camera_mode(); } -void Camera3D::set_size(float p_size) { +void Camera3D::set_size(real_t p_size) { ERR_FAIL_COND(p_size < 0.1 || p_size > 16384); size = p_size; _update_camera_mode(); } -void Camera3D::set_near(float p_near) { +void Camera3D::set_near(real_t p_near) { near = p_near; _update_camera_mode(); } @@ -577,7 +575,7 @@ void Camera3D::set_frustum_offset(Vector2 p_offset) { _update_camera_mode(); } -void Camera3D::set_far(float p_far) { +void Camera3D::set_far(real_t p_far) { far = p_far; _update_camera_mode(); } @@ -592,18 +590,22 @@ uint32_t Camera3D::get_cull_mask() const { return layers; } -void Camera3D::set_cull_mask_bit(int p_layer, bool p_enable) { - ERR_FAIL_INDEX(p_layer, 32); - if (p_enable) { - set_cull_mask(layers | (1 << p_layer)); +void Camera3D::set_cull_mask_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Render layer number must be between 1 and 20 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 20, "Render layer number must be between 1 and 20 inclusive."); + uint32_t mask = get_cull_mask(); + if (p_value) { + mask |= 1 << (p_layer_number - 1); } else { - set_cull_mask(layers & (~(1 << p_layer))); + mask &= ~(1 << (p_layer_number - 1)); } + set_cull_mask(mask); } -bool Camera3D::get_cull_mask_bit(int p_layer) const { - ERR_FAIL_INDEX_V(p_layer, 32, false); - return (layers & (1 << p_layer)); +bool Camera3D::get_cull_mask_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Render layer number must be between 1 and 20 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 20, false, "Render layer number must be between 1 and 20 inclusive."); + return layers & (1 << (p_layer_number - 1)); } Vector<Plane> Camera3D::get_frustum() const { @@ -630,21 +632,21 @@ bool Camera3D::is_position_in_frustum(const Vector3 &p_position) const { return true; } -void Camera3D::set_v_offset(float p_offset) { +void Camera3D::set_v_offset(real_t p_offset) { v_offset = p_offset; _update_camera(); } -float Camera3D::get_v_offset() const { +real_t Camera3D::get_v_offset() const { return v_offset; } -void Camera3D::set_h_offset(float p_offset) { +void Camera3D::set_h_offset(real_t p_offset) { h_offset = p_offset; _update_camera(); } -float Camera3D::get_h_offset() const { +real_t Camera3D::get_h_offset() const { return h_offset; } @@ -672,11 +674,11 @@ Camera3D::~Camera3D() { //////////////////////////////////////// -void ClippedCamera3D::set_margin(float p_margin) { +void ClippedCamera3D::set_margin(real_t p_margin) { margin = p_margin; } -float ClippedCamera3D::get_margin() const { +real_t ClippedCamera3D::get_margin() const { return margin; } @@ -746,7 +748,7 @@ void ClippedCamera3D::_notification(int p_what) { xf.origin = ray_from; xf.orthonormalize(); - float closest_safe = 1.0f, closest_unsafe = 1.0f; + real_t closest_safe = 1.0f, closest_unsafe = 1.0f; if (dspace->cast_motion(pyramid_shape, xf, cam_pos - ray_from, margin, closest_safe, closest_unsafe, exclude, collision_mask, clip_to_bodies, clip_to_areas)) { clip_offset = cam_pos.distance_to(ray_from + (cam_pos - ray_from) * closest_safe); } @@ -755,7 +757,7 @@ void ClippedCamera3D::_notification(int p_what) { } if (p_what == NOTIFICATION_LOCAL_TRANSFORM_CHANGED) { - update_gizmo(); + update_gizmos(); } } @@ -767,20 +769,22 @@ uint32_t ClippedCamera3D::get_collision_mask() const { return collision_mask; } -void ClippedCamera3D::set_collision_mask_bit(int p_bit, bool p_value) { - ERR_FAIL_INDEX_MSG(p_bit, 32, "Collision layer bit must be between 0 and 31 inclusive."); +void ClippedCamera3D::set_collision_mask_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive."); uint32_t mask = get_collision_mask(); if (p_value) { - mask |= 1 << p_bit; + mask |= 1 << (p_layer_number - 1); } else { - mask &= ~(1 << p_bit); + mask &= ~(1 << (p_layer_number - 1)); } set_collision_mask(mask); } -bool ClippedCamera3D::get_collision_mask_bit(int p_bit) const { - ERR_FAIL_INDEX_V_MSG(p_bit, 32, false, "Collision mask bit must be between 0 and 31 inclusive."); - return get_collision_mask() & (1 << p_bit); +bool ClippedCamera3D::get_collision_mask_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Collision layer number must be between 1 and 32 inclusive."); + return get_collision_mask() & (1 << (p_layer_number - 1)); } void ClippedCamera3D::add_exception_rid(const RID &p_rid) { @@ -813,7 +817,7 @@ void ClippedCamera3D::clear_exceptions() { exclude.clear(); } -float ClippedCamera3D::get_clip_offset() const { +real_t ClippedCamera3D::get_clip_offset() const { return clip_offset; } @@ -843,8 +847,8 @@ void ClippedCamera3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &ClippedCamera3D::set_collision_mask); ClassDB::bind_method(D_METHOD("get_collision_mask"), &ClippedCamera3D::get_collision_mask); - ClassDB::bind_method(D_METHOD("set_collision_mask_bit", "bit", "value"), &ClippedCamera3D::set_collision_mask_bit); - ClassDB::bind_method(D_METHOD("get_collision_mask_bit", "bit"), &ClippedCamera3D::get_collision_mask_bit); + ClassDB::bind_method(D_METHOD("set_collision_mask_value", "layer_number", "value"), &ClippedCamera3D::set_collision_mask_value); + ClassDB::bind_method(D_METHOD("get_collision_mask_value", "layer_number"), &ClippedCamera3D::get_collision_mask_value); ClassDB::bind_method(D_METHOD("add_exception_rid", "rid"), &ClippedCamera3D::add_exception_rid); ClassDB::bind_method(D_METHOD("add_exception", "node"), &ClippedCamera3D::add_exception); diff --git a/scene/3d/camera_3d.h b/scene/3d/camera_3d.h index c6efc8f9a9..3b704944b0 100644 --- a/scene/3d/camera_3d.h +++ b/scene/3d/camera_3d.h @@ -33,9 +33,6 @@ #include "scene/3d/node_3d.h" #include "scene/3d/velocity_tracker_3d.h" -#include "scene/main/window.h" -#include "scene/resources/camera_effects.h" -#include "scene/resources/environment.h" class Camera3D : public Node3D { GDCLASS(Camera3D, Node3D); @@ -63,13 +60,13 @@ private: Projection mode = PROJECTION_PERSPECTIVE; - float fov = 0.0; - float size = 1.0; + real_t fov = 0.0; + real_t size = 1.0; Vector2 frustum_offset; - float near = 0.0; - float far = 0.0; - float v_offset = 0.0; - float h_offset = 0.0; + real_t near = 0.0; + real_t far = 0.0; + real_t v_offset = 0.0; + real_t h_offset = 0.0; KeepAspect keep_aspect = KEEP_HEIGHT; RID camera; @@ -107,10 +104,9 @@ public: NOTIFICATION_LOST_CURRENT = 51 }; - void set_perspective(float p_fovy_degrees, float p_z_near, float p_z_far); - void set_orthogonal(float p_size, float p_z_near, float p_z_far); - void set_frustum(float p_size, Vector2 p_offset, float p_z_near, - float p_z_far); + void set_perspective(real_t p_fovy_degrees, real_t p_z_near, real_t p_z_far); + void set_orthogonal(real_t p_size, real_t p_z_near, real_t p_z_far); + void set_frustum(real_t p_size, Vector2 p_offset, real_t p_z_near, real_t p_z_far); void set_projection(Camera3D::Projection p_mode); void make_current(); @@ -120,18 +116,18 @@ public: RID get_camera() const; - float get_fov() const; - float get_size() const; - float get_far() const; - float get_near() const; + real_t get_fov() const; + real_t get_size() const; + real_t get_far() const; + real_t get_near() const; Vector2 get_frustum_offset() const; Projection get_projection() const; - void set_fov(float p_fov); - void set_size(float p_size); - void set_far(float p_far); - void set_near(float p_near); + void set_fov(real_t p_fov); + void set_size(real_t p_size); + void set_far(real_t p_far); + void set_near(real_t p_near); void set_frustum_offset(Vector2 p_offset); virtual Transform3D get_camera_transform() const; @@ -141,16 +137,15 @@ public: virtual Vector3 project_local_ray_normal(const Point2 &p_pos) const; virtual Point2 unproject_position(const Vector3 &p_pos) const; bool is_position_behind(const Vector3 &p_pos) const; - virtual Vector3 project_position(const Point2 &p_point, - float p_z_depth) const; + virtual Vector3 project_position(const Point2 &p_point, real_t p_z_depth) const; Vector<Vector3> get_near_plane_points() const; void set_cull_mask(uint32_t p_layers); uint32_t get_cull_mask() const; - void set_cull_mask_bit(int p_layer, bool p_enable); - bool get_cull_mask_bit(int p_layer) const; + void set_cull_mask_value(int p_layer_number, bool p_enable); + bool get_cull_mask_value(int p_layer_number) const; virtual Vector<Plane> get_frustum() const; bool is_position_in_frustum(const Vector3 &p_position) const; @@ -164,11 +159,11 @@ public: void set_keep_aspect_mode(KeepAspect p_aspect); KeepAspect get_keep_aspect_mode() const; - void set_v_offset(float p_offset); - float get_v_offset() const; + void set_v_offset(real_t p_offset); + real_t get_v_offset() const; - void set_h_offset(float p_offset); - float get_h_offset() const; + void set_h_offset(real_t p_offset); + real_t get_h_offset() const; void set_doppler_tracking(DopplerTracking p_tracking); DopplerTracking get_doppler_tracking() const; @@ -195,8 +190,8 @@ public: private: ClipProcessCallback process_callback = CLIP_PROCESS_PHYSICS; RID pyramid_shape; - float margin = 0.0; - float clip_offset = 0.0; + real_t margin = 0.0; + real_t clip_offset = 0.0; uint32_t collision_mask = 1; bool clip_to_areas = false; bool clip_to_bodies = true; @@ -217,8 +212,8 @@ public: void set_clip_to_bodies(bool p_clip); bool is_clip_to_bodies_enabled() const; - void set_margin(float p_margin); - float get_margin() const; + void set_margin(real_t p_margin); + real_t get_margin() const; void set_process_callback(ClipProcessCallback p_mode); ClipProcessCallback get_process_callback() const; @@ -226,8 +221,8 @@ public: void set_collision_mask(uint32_t p_mask); uint32_t get_collision_mask() const; - void set_collision_mask_bit(int p_bit, bool p_value); - bool get_collision_mask_bit(int p_bit) const; + void set_collision_mask_value(int p_layer_number, bool p_value); + bool get_collision_mask_value(int p_layer_number) const; void add_exception_rid(const RID &p_rid); void add_exception(const Object *p_object); @@ -235,7 +230,7 @@ public: void remove_exception(const Object *p_object); void clear_exceptions(); - float get_clip_offset() const; + real_t get_clip_offset() const; ClippedCamera3D(); ~ClippedCamera3D(); diff --git a/scene/3d/collision_object_3d.cpp b/scene/3d/collision_object_3d.cpp index 40e3f7c764..e2f953974a 100644 --- a/scene/3d/collision_object_3d.cpp +++ b/scene/3d/collision_object_3d.cpp @@ -30,7 +30,6 @@ #include "collision_object_3d.h" -#include "core/config/engine.h" #include "scene/scene_string_names.h" void CollisionObject3D::_notification(int p_what) { @@ -77,6 +76,10 @@ void CollisionObject3D::_notification(int p_what) { } break; case NOTIFICATION_TRANSFORM_CHANGED: { + if (only_update_transform_changes) { + return; + } + if (area) { PhysicsServer3D::get_singleton()->area_set_transform(rid, get_global_transform()); } else { @@ -142,36 +145,40 @@ uint32_t CollisionObject3D::get_collision_mask() const { return collision_mask; } -void CollisionObject3D::set_collision_layer_bit(int p_bit, bool p_value) { - ERR_FAIL_INDEX_MSG(p_bit, 32, "Collision layer bit must be between 0 and 31 inclusive."); +void CollisionObject3D::set_collision_layer_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive."); uint32_t collision_layer = get_collision_layer(); if (p_value) { - collision_layer |= 1 << p_bit; + collision_layer |= 1 << (p_layer_number - 1); } else { - collision_layer &= ~(1 << p_bit); + collision_layer &= ~(1 << (p_layer_number - 1)); } set_collision_layer(collision_layer); } -bool CollisionObject3D::get_collision_layer_bit(int p_bit) const { - ERR_FAIL_INDEX_V_MSG(p_bit, 32, false, "Collision layer bit must be between 0 and 31 inclusive."); - return get_collision_layer() & (1 << p_bit); +bool CollisionObject3D::get_collision_layer_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Collision layer number must be between 1 and 32 inclusive."); + return get_collision_layer() & (1 << (p_layer_number - 1)); } -void CollisionObject3D::set_collision_mask_bit(int p_bit, bool p_value) { - ERR_FAIL_INDEX_MSG(p_bit, 32, "Collision mask bit must be between 0 and 31 inclusive."); +void CollisionObject3D::set_collision_mask_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive."); uint32_t mask = get_collision_mask(); if (p_value) { - mask |= 1 << p_bit; + mask |= 1 << (p_layer_number - 1); } else { - mask &= ~(1 << p_bit); + mask &= ~(1 << (p_layer_number - 1)); } set_collision_mask(mask); } -bool CollisionObject3D::get_collision_mask_bit(int p_bit) const { - ERR_FAIL_INDEX_V_MSG(p_bit, 32, false, "Collision mask bit must be between 0 and 31 inclusive."); - return get_collision_mask() & (1 << p_bit); +bool CollisionObject3D::get_collision_mask_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Collision layer number must be between 1 and 32 inclusive."); + return get_collision_mask() & (1 << (p_layer_number - 1)); } void CollisionObject3D::set_disable_mode(DisableMode p_mode) { @@ -247,10 +254,8 @@ void CollisionObject3D::_apply_enabled() { } } -void CollisionObject3D::_input_event(Node *p_camera, const Ref<InputEvent> &p_input_event, const Vector3 &p_pos, const Vector3 &p_normal, int p_shape) { - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_input_event, p_camera, p_input_event, p_pos, p_normal, p_shape); - } +void CollisionObject3D::_input_event_call(Camera3D *p_camera, const Ref<InputEvent> &p_input_event, const Vector3 &p_pos, const Vector3 &p_normal, int p_shape) { + GDVIRTUAL_CALL(_input_event, p_camera, p_input_event, p_pos, p_normal, p_shape); emit_signal(SceneStringNames::get_singleton()->input_event, p_camera, p_input_event, p_pos, p_normal, p_shape); } @@ -284,6 +289,14 @@ void CollisionObject3D::set_body_mode(PhysicsServer3D::BodyMode p_mode) { PhysicsServer3D::get_singleton()->body_set_mode(rid, p_mode); } +void CollisionObject3D::set_only_update_transform_changes(bool p_enable) { + only_update_transform_changes = p_enable; +} + +bool CollisionObject3D::is_only_update_transform_changes_enabled() const { + return only_update_transform_changes; +} + void CollisionObject3D::_update_pickable() { if (!is_inside_tree()) { return; @@ -411,10 +424,10 @@ void CollisionObject3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_collision_layer"), &CollisionObject3D::get_collision_layer); ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &CollisionObject3D::set_collision_mask); ClassDB::bind_method(D_METHOD("get_collision_mask"), &CollisionObject3D::get_collision_mask); - ClassDB::bind_method(D_METHOD("set_collision_layer_bit", "bit", "value"), &CollisionObject3D::set_collision_layer_bit); - ClassDB::bind_method(D_METHOD("get_collision_layer_bit", "bit"), &CollisionObject3D::get_collision_layer_bit); - ClassDB::bind_method(D_METHOD("set_collision_mask_bit", "bit", "value"), &CollisionObject3D::set_collision_mask_bit); - ClassDB::bind_method(D_METHOD("get_collision_mask_bit", "bit"), &CollisionObject3D::get_collision_mask_bit); + ClassDB::bind_method(D_METHOD("set_collision_layer_value", "layer_number", "value"), &CollisionObject3D::set_collision_layer_value); + ClassDB::bind_method(D_METHOD("get_collision_layer_value", "layer_number"), &CollisionObject3D::get_collision_layer_value); + ClassDB::bind_method(D_METHOD("set_collision_mask_value", "layer_number", "value"), &CollisionObject3D::set_collision_mask_value); + ClassDB::bind_method(D_METHOD("get_collision_mask_value", "layer_number"), &CollisionObject3D::get_collision_mask_value); ClassDB::bind_method(D_METHOD("set_disable_mode", "mode"), &CollisionObject3D::set_disable_mode); ClassDB::bind_method(D_METHOD("get_disable_mode"), &CollisionObject3D::get_disable_mode); ClassDB::bind_method(D_METHOD("set_ray_pickable", "ray_pickable"), &CollisionObject3D::set_ray_pickable); @@ -438,7 +451,7 @@ void CollisionObject3D::_bind_methods() { ClassDB::bind_method(D_METHOD("shape_owner_clear_shapes", "owner_id"), &CollisionObject3D::shape_owner_clear_shapes); ClassDB::bind_method(D_METHOD("shape_find_owner", "shape_index"), &CollisionObject3D::shape_find_owner); - BIND_VMETHOD(MethodInfo("_input_event", PropertyInfo(Variant::OBJECT, "camera"), PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"), PropertyInfo(Variant::VECTOR3, "position"), PropertyInfo(Variant::VECTOR3, "normal"), PropertyInfo(Variant::INT, "shape_idx"))); + GDVIRTUAL_BIND(_input_event, "camera", "event", "position", "normal", "shape_idx"); ADD_SIGNAL(MethodInfo("input_event", PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"), PropertyInfo(Variant::VECTOR3, "position"), PropertyInfo(Variant::VECTOR3, "normal"), PropertyInfo(Variant::INT, "shape_idx"))); ADD_SIGNAL(MethodInfo("mouse_entered")); diff --git a/scene/3d/collision_object_3d.h b/scene/3d/collision_object_3d.h index a3a890db75..1c7e205888 100644 --- a/scene/3d/collision_object_3d.h +++ b/scene/3d/collision_object_3d.h @@ -31,9 +31,8 @@ #ifndef COLLISION_OBJECT_3D_H #define COLLISION_OBJECT_3D_H +#include "scene/3d/camera_3d.h" #include "scene/3d/node_3d.h" -#include "scene/resources/shape_3d.h" -#include "servers/physics_server_3d.h" class CollisionObject3D : public Node3D { GDCLASS(CollisionObject3D, Node3D); @@ -74,6 +73,8 @@ private: Map<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; @@ -101,12 +102,16 @@ protected: void _on_transform_changed(); friend class Viewport; - virtual void _input_event(Node *p_camera, const Ref<InputEvent> &p_input_event, const Vector3 &p_pos, const Vector3 &p_normal, int p_shape); + virtual void _input_event_call(Camera3D *p_camera, const Ref<InputEvent> &p_input_event, const Vector3 &p_pos, const Vector3 &p_normal, int p_shape); virtual void _mouse_enter(); virtual void _mouse_exit(); void set_body_mode(PhysicsServer3D::BodyMode p_mode); + void set_only_update_transform_changes(bool p_enable); + bool is_only_update_transform_changes_enabled() const; + + GDVIRTUAL5(_input_event, Camera3D *, Ref<InputEvent>, Vector3, Vector3, int) public: void set_collision_layer(uint32_t p_layer); uint32_t get_collision_layer() const; @@ -114,11 +119,11 @@ public: void set_collision_mask(uint32_t p_mask); uint32_t get_collision_mask() const; - void set_collision_layer_bit(int p_bit, bool p_value); - bool get_collision_layer_bit(int p_bit) const; + void set_collision_layer_value(int p_layer_number, bool p_value); + bool get_collision_layer_value(int p_layer_number) const; - void set_collision_mask_bit(int p_bit, bool p_value); - bool get_collision_mask_bit(int p_bit) const; + void set_collision_mask_value(int p_layer_number, bool p_value); + bool get_collision_mask_value(int p_layer_number) const; void set_disable_mode(DisableMode p_mode); DisableMode get_disable_mode() const; diff --git a/scene/3d/collision_polygon_3d.cpp b/scene/3d/collision_polygon_3d.cpp index 8a4f8b639b..d5835586f9 100644 --- a/scene/3d/collision_polygon_3d.cpp +++ b/scene/3d/collision_polygon_3d.cpp @@ -32,7 +32,6 @@ #include "collision_object_3d.h" #include "core/math/geometry_2d.h" -#include "scene/resources/concave_polygon_shape_3d.h" #include "scene/resources/convex_polygon_shape_3d.h" void CollisionPolygon3D::_build_polygon() { @@ -122,7 +121,7 @@ void CollisionPolygon3D::set_polygon(const Vector<Point2> &p_polygon) { _build_polygon(); } update_configuration_warnings(); - update_gizmo(); + update_gizmos(); } Vector<Point2> CollisionPolygon3D::get_polygon() const { @@ -136,7 +135,7 @@ AABB CollisionPolygon3D::get_item_rect() const { void CollisionPolygon3D::set_depth(real_t p_depth) { depth = p_depth; _build_polygon(); - update_gizmo(); + update_gizmos(); } real_t CollisionPolygon3D::get_depth() const { @@ -145,7 +144,7 @@ real_t CollisionPolygon3D::get_depth() const { void CollisionPolygon3D::set_disabled(bool p_disabled) { disabled = p_disabled; - update_gizmo(); + update_gizmos(); if (parent) { parent->shape_owner_set_disabled(owner_id, p_disabled); diff --git a/scene/3d/collision_shape_3d.cpp b/scene/3d/collision_shape_3d.cpp index be3fde8013..d4668a24f2 100644 --- a/scene/3d/collision_shape_3d.cpp +++ b/scene/3d/collision_shape_3d.cpp @@ -30,19 +30,10 @@ #include "collision_shape_3d.h" -#include "core/math/quick_hull.h" #include "mesh_instance_3d.h" #include "physics_body_3d.h" -#include "scene/resources/box_shape_3d.h" -#include "scene/resources/capsule_shape_3d.h" #include "scene/resources/concave_polygon_shape_3d.h" #include "scene/resources/convex_polygon_shape_3d.h" -#include "scene/resources/ray_shape_3d.h" -#include "scene/resources/sphere_shape_3d.h" -#include "scene/resources/world_margin_shape_3d.h" -#include "servers/rendering_server.h" - -//TODO: Implement CylinderShape and HeightMapShape? void CollisionShape3D::make_convex_from_siblings() { Node *p = get_parent(); @@ -117,7 +108,7 @@ void CollisionShape3D::_notification(int p_what) { } void CollisionShape3D::resource_changed(RES res) { - update_gizmo(); + update_gizmos(); } TypedArray<String> CollisionShape3D::get_configuration_warnings() const { @@ -166,7 +157,7 @@ void CollisionShape3D::set_shape(const Ref<Shape3D> &p_shape) { if (!shape.is_null()) { shape->register_owner(this); } - update_gizmo(); + update_gizmos(); if (parent) { parent->shape_owner_clear_shapes(owner_id); if (shape.is_valid()) { @@ -187,7 +178,7 @@ Ref<Shape3D> CollisionShape3D::get_shape() const { void CollisionShape3D::set_disabled(bool p_disabled) { disabled = p_disabled; - update_gizmo(); + update_gizmos(); if (parent) { parent->shape_owner_set_disabled(owner_id, p_disabled); } diff --git a/scene/3d/collision_shape_3d.h b/scene/3d/collision_shape_3d.h index f69c1e38eb..cb7fe21eae 100644 --- a/scene/3d/collision_shape_3d.h +++ b/scene/3d/collision_shape_3d.h @@ -33,6 +33,7 @@ #include "scene/3d/node_3d.h" #include "scene/resources/shape_3d.h" + class CollisionObject3D; class CollisionShape3D : public Node3D { GDCLASS(CollisionShape3D, Node3D); diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp index 6dc865ec0e..48ef41e015 100644 --- a/scene/3d/cpu_particles_3d.cpp +++ b/scene/3d/cpu_particles_3d.cpp @@ -32,8 +32,8 @@ #include "scene/3d/camera_3d.h" #include "scene/3d/gpu_particles_3d.h" +#include "scene/main/viewport.h" #include "scene/resources/particles_material.h" -#include "servers/rendering_server.h" AABB CPUParticles3D::get_aabb() const { return AABB(); @@ -78,7 +78,7 @@ void CPUParticles3D::set_amount(int p_amount) { particle_order.resize(p_amount); } -void CPUParticles3D::set_lifetime(float p_lifetime) { +void CPUParticles3D::set_lifetime(double p_lifetime) { ERR_FAIL_COND_MSG(p_lifetime <= 0, "Particles lifetime must be greater than 0."); lifetime = p_lifetime; } @@ -87,19 +87,19 @@ void CPUParticles3D::set_one_shot(bool p_one_shot) { one_shot = p_one_shot; } -void CPUParticles3D::set_pre_process_time(float p_time) { +void CPUParticles3D::set_pre_process_time(double p_time) { pre_process_time = p_time; } -void CPUParticles3D::set_explosiveness_ratio(float p_ratio) { +void CPUParticles3D::set_explosiveness_ratio(real_t p_ratio) { explosiveness_ratio = p_ratio; } -void CPUParticles3D::set_randomness_ratio(float p_ratio) { +void CPUParticles3D::set_randomness_ratio(real_t p_ratio) { randomness_ratio = p_ratio; } -void CPUParticles3D::set_lifetime_randomness(float p_random) { +void CPUParticles3D::set_lifetime_randomness(double p_random) { lifetime_randomness = p_random; } @@ -107,7 +107,7 @@ void CPUParticles3D::set_use_local_coordinates(bool p_enable) { local_coords = p_enable; } -void CPUParticles3D::set_speed_scale(float p_scale) { +void CPUParticles3D::set_speed_scale(double p_scale) { speed_scale = p_scale; } @@ -119,7 +119,7 @@ int CPUParticles3D::get_amount() const { return particles.size(); } -float CPUParticles3D::get_lifetime() const { +double CPUParticles3D::get_lifetime() const { return lifetime; } @@ -127,19 +127,19 @@ bool CPUParticles3D::get_one_shot() const { return one_shot; } -float CPUParticles3D::get_pre_process_time() const { +double CPUParticles3D::get_pre_process_time() const { return pre_process_time; } -float CPUParticles3D::get_explosiveness_ratio() const { +real_t CPUParticles3D::get_explosiveness_ratio() const { return explosiveness_ratio; } -float CPUParticles3D::get_randomness_ratio() const { +real_t CPUParticles3D::get_randomness_ratio() const { return randomness_ratio; } -float CPUParticles3D::get_lifetime_randomness() const { +double CPUParticles3D::get_lifetime_randomness() const { return lifetime_randomness; } @@ -147,7 +147,7 @@ bool CPUParticles3D::get_use_local_coordinates() const { return local_coords; } -float CPUParticles3D::get_speed_scale() const { +double CPUParticles3D::get_speed_scale() const { return speed_scale; } @@ -212,7 +212,7 @@ TypedArray<String> CPUParticles3D::get_configuration_warnings() const { warnings.push_back(TTR("Nothing is visible because no mesh has been assigned.")); } - if (!anim_material_found && (get_param(PARAM_ANIM_SPEED) != 0.0 || get_param(PARAM_ANIM_OFFSET) != 0.0 || + if (!anim_material_found && (get_param_max(PARAM_ANIM_SPEED) != 0.0 || get_param_max(PARAM_ANIM_OFFSET) != 0.0 || get_param_curve(PARAM_ANIM_SPEED).is_valid() || get_param_curve(PARAM_ANIM_OFFSET).is_valid())) { warnings.push_back(TTR("CPUParticles3D animation requires the usage of a StandardMaterial3D whose Billboard Mode is set to \"Particle Billboard\".")); } @@ -247,47 +247,52 @@ Vector3 CPUParticles3D::get_direction() const { return direction; } -void CPUParticles3D::set_spread(float p_spread) { +void CPUParticles3D::set_spread(real_t p_spread) { spread = p_spread; } -float CPUParticles3D::get_spread() const { +real_t CPUParticles3D::get_spread() const { return spread; } -void CPUParticles3D::set_flatness(float p_flatness) { +void CPUParticles3D::set_flatness(real_t p_flatness) { flatness = p_flatness; } -float CPUParticles3D::get_flatness() const { +real_t CPUParticles3D::get_flatness() const { return flatness; } -void CPUParticles3D::set_param(Parameter p_param, float p_value) { +void CPUParticles3D::set_param_min(Parameter p_param, real_t p_value) { ERR_FAIL_INDEX(p_param, PARAM_MAX); - parameters[p_param] = p_value; + parameters_min[p_param] = p_value; + if (parameters_min[p_param] > parameters_max[p_param]) { + set_param_max(p_param, p_value); + } } -float CPUParticles3D::get_param(Parameter p_param) const { +real_t CPUParticles3D::get_param_min(Parameter p_param) const { ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0); - return parameters[p_param]; + return parameters_min[p_param]; } -void CPUParticles3D::set_param_randomness(Parameter p_param, float p_value) { +void CPUParticles3D::set_param_max(Parameter p_param, real_t p_value) { ERR_FAIL_INDEX(p_param, PARAM_MAX); - - randomness[p_param] = p_value; + parameters_max[p_param] = p_value; + if (parameters_min[p_param] > parameters_max[p_param]) { + set_param_min(p_param, p_value); + } } -float CPUParticles3D::get_param_randomness(Parameter p_param) const { +real_t CPUParticles3D::get_param_max(Parameter p_param) const { ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0); - return randomness[p_param]; + return parameters_max[p_param]; } -static void _adjust_curve_range(const Ref<Curve> &p_curve, float p_min, float p_max) { +static void _adjust_curve_range(const Ref<Curve> &p_curve, real_t p_min, real_t p_max) { Ref<Curve> curve = p_curve; if (!curve.is_valid()) { return; @@ -381,7 +386,7 @@ void CPUParticles3D::set_emission_shape(EmissionShape p_shape) { emission_shape = p_shape; } -void CPUParticles3D::set_emission_sphere_radius(float p_radius) { +void CPUParticles3D::set_emission_sphere_radius(real_t p_radius) { emission_sphere_radius = p_radius; } @@ -401,7 +406,40 @@ void CPUParticles3D::set_emission_colors(const Vector<Color> &p_colors) { emission_colors = p_colors; } -float CPUParticles3D::get_emission_sphere_radius() const { +void CPUParticles3D::set_emission_ring_axis(Vector3 p_axis) { + emission_ring_axis = p_axis; +} + +void CPUParticles3D::set_emission_ring_height(real_t p_height) { + emission_ring_height = p_height; +} + +void CPUParticles3D::set_emission_ring_radius(real_t p_radius) { + emission_ring_radius = p_radius; +} + +void CPUParticles3D::set_emission_ring_inner_radius(real_t p_radius) { + emission_ring_inner_radius = p_radius; +} + +void CPUParticles3D::set_scale_curve_x(Ref<Curve> p_scale_curve) { + scale_curve_x = p_scale_curve; +} + +void CPUParticles3D::set_scale_curve_y(Ref<Curve> p_scale_curve) { + scale_curve_y = p_scale_curve; +} + +void CPUParticles3D::set_scale_curve_z(Ref<Curve> p_scale_curve) { + scale_curve_z = p_scale_curve; +} + +void CPUParticles3D::set_split_scale(bool p_split_scale) { + split_scale = p_split_scale; + notify_property_list_changed(); +} + +real_t CPUParticles3D::get_emission_sphere_radius() const { return emission_sphere_radius; } @@ -421,6 +459,22 @@ Vector<Color> CPUParticles3D::get_emission_colors() const { return emission_colors; } +Vector3 CPUParticles3D::get_emission_ring_axis() const { + return emission_ring_axis; +} + +real_t CPUParticles3D::get_emission_ring_height() const { + return emission_ring_height; +} + +real_t CPUParticles3D::get_emission_ring_radius() const { + return emission_ring_radius; +} + +real_t CPUParticles3D::get_emission_ring_inner_radius() const { + return emission_ring_inner_radius; +} + CPUParticles3D::EmissionShape CPUParticles3D::get_emission_shape() const { return emission_shape; } @@ -433,11 +487,23 @@ Vector3 CPUParticles3D::get_gravity() const { return gravity; } -void CPUParticles3D::_validate_property(PropertyInfo &property) const { - if (property.name == "color" && color_ramp.is_valid()) { - property.usage = PROPERTY_USAGE_NONE; - } +Ref<Curve> CPUParticles3D::get_scale_curve_x() const { + return scale_curve_x; +} + +Ref<Curve> CPUParticles3D::get_scale_curve_y() const { + return scale_curve_y; +} + +Ref<Curve> CPUParticles3D::get_scale_curve_z() const { + return scale_curve_z; +} + +bool CPUParticles3D::get_split_scale() { + return split_scale; +} +void CPUParticles3D::_validate_property(PropertyInfo &property) const { if (property.name == "emission_sphere_radius" && emission_shape != EMISSION_SHAPE_SPHERE) { property.usage = PROPERTY_USAGE_NONE; } @@ -446,7 +512,7 @@ void CPUParticles3D::_validate_property(PropertyInfo &property) const { property.usage = PROPERTY_USAGE_NONE; } - if ((property.name == "emission_point_texture" || property.name == "emission_color_texture") && (emission_shape < EMISSION_SHAPE_POINTS)) { + if ((property.name == "emission_point_texture" || property.name == "emission_color_texture" || property.name == "emission_points") && (emission_shape != EMISSION_SHAPE_POINTS && (emission_shape != EMISSION_SHAPE_DIRECTED_POINTS))) { property.usage = PROPERTY_USAGE_NONE; } @@ -454,9 +520,17 @@ void CPUParticles3D::_validate_property(PropertyInfo &property) const { property.usage = PROPERTY_USAGE_NONE; } + if (property.name.begins_with("emission_ring_") && emission_shape != EMISSION_SHAPE_RING) { + property.usage = PROPERTY_USAGE_NONE; + } + if (property.name.begins_with("orbit_") && !particle_flags[PARTICLE_FLAG_DISABLE_Z]) { property.usage = PROPERTY_USAGE_NONE; } + + if (property.name.begins_with("scale_curve_") && !split_scale) { + property.usage = PROPERTY_USAGE_NONE; + } } static uint32_t idhash(uint32_t x) { @@ -466,7 +540,7 @@ static uint32_t idhash(uint32_t x) { return x; } -static float rand_from_seed(uint32_t &seed) { +static real_t rand_from_seed(uint32_t &seed) { int k; int s = int(seed); if (s == 0) { @@ -478,7 +552,7 @@ static float rand_from_seed(uint32_t &seed) { s += 2147483647; } seed = uint32_t(s); - return float(seed % uint32_t(65536)) / 65535.0; + return (seed % uint32_t(65536)) / 65535.0; } void CPUParticles3D::_update_internal() { @@ -487,7 +561,7 @@ void CPUParticles3D::_update_internal() { return; } - float delta = get_process_delta_time(); + double delta = get_process_delta_time(); if (emitting) { inactive_time = 0; } else { @@ -509,14 +583,14 @@ void CPUParticles3D::_update_internal() { bool processed = false; if (time == 0 && pre_process_time > 0.0) { - float frame_time; + double frame_time; if (fixed_fps > 0) { frame_time = 1.0 / fixed_fps; } else { frame_time = 1.0 / 30.0; } - float todo = pre_process_time; + double todo = pre_process_time; while (todo >= 0) { _particles_process(frame_time); @@ -526,16 +600,16 @@ void CPUParticles3D::_update_internal() { } if (fixed_fps > 0) { - float frame_time = 1.0 / fixed_fps; - float decr = frame_time; + double frame_time = 1.0 / fixed_fps; + double decr = frame_time; - float ldelta = delta; + double ldelta = delta; if (ldelta > 0.1) { //avoid recursive stalls if fps goes below 10 ldelta = 0.1; } else if (ldelta <= 0.0) { //unlikely but.. ldelta = 0.001; } - float todo = frame_remainder + ldelta; + double todo = frame_remainder + ldelta; while (todo >= frame_time) { _particles_process(frame_time); @@ -555,7 +629,7 @@ void CPUParticles3D::_update_internal() { } } -void CPUParticles3D::_particles_process(float p_delta) { +void CPUParticles3D::_particles_process(double p_delta) { p_delta *= speed_scale; int pcount = particles.size(); @@ -563,7 +637,7 @@ void CPUParticles3D::_particles_process(float p_delta) { Particle *parray = w; - float prev_time = time; + double prev_time = time; time += p_delta; if (time > lifetime) { time = Math::fmod(time, lifetime); @@ -581,7 +655,7 @@ void CPUParticles3D::_particles_process(float p_delta) { velocity_xform = emission_xform.basis; } - float system_phase = time / lifetime; + double system_phase = time / lifetime; for (int i = 0; i < pcount; i++) { Particle &p = parray[i]; @@ -590,12 +664,12 @@ void CPUParticles3D::_particles_process(float p_delta) { continue; } - float local_delta = p_delta; + double local_delta = p_delta; // The phase is a ratio between 0 (birth) and 1 (end of life) for each particle. // While we use time in tests later on, for randomness we use the phase as done in the // original shader code, and we later multiply by lifetime to get the time. - float restart_phase = float(i) / float(pcount); + double restart_phase = double(i) / double(pcount); if (randomness_ratio > 0.0) { uint32_t seed = cycle; @@ -604,12 +678,12 @@ void CPUParticles3D::_particles_process(float p_delta) { } seed *= uint32_t(pcount); seed += uint32_t(i); - float random = float(idhash(seed) % uint32_t(65536)) / 65536.0; - restart_phase += randomness_ratio * random * 1.0 / float(pcount); + double random = double(idhash(seed) % uint32_t(65536)) / 65536.0; + restart_phase += randomness_ratio * random * 1.0 / double(pcount); } restart_phase *= (1.0 - explosiveness_ratio); - float restart_time = restart_phase * lifetime; + double restart_time = restart_phase * lifetime; bool restart = false; if (time > prev_time) { @@ -650,17 +724,17 @@ void CPUParticles3D::_particles_process(float p_delta) { } p.active = true; - /*float tex_linear_velocity = 0; + /*real_t tex_linear_velocity = 0; if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { tex_linear_velocity = curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY]->interpolate(0); }*/ - float tex_angle = 0.0; + real_t tex_angle = 0.0; if (curve_parameters[PARAM_ANGLE].is_valid()) { tex_angle = curve_parameters[PARAM_ANGLE]->interpolate(tv); } - float tex_anim_offset = 0.0; + real_t tex_anim_offset = 0.0; if (curve_parameters[PARAM_ANGLE].is_valid()) { tex_anim_offset = curve_parameters[PARAM_ANGLE]->interpolate(tv); } @@ -673,26 +747,39 @@ void CPUParticles3D::_particles_process(float p_delta) { p.anim_offset_rand = Math::randf(); if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { - float angle1_rad = Math::atan2(direction.y, direction.x) + Math::deg2rad((Math::randf() * 2.0 - 1.0) * spread); + real_t angle1_rad = Math::atan2(direction.y, direction.x) + Math::deg2rad((Math::randf() * 2.0 - 1.0) * spread); Vector3 rot = Vector3(Math::cos(angle1_rad), Math::sin(angle1_rad), 0.0); - p.velocity = rot * parameters[PARAM_INITIAL_LINEAR_VELOCITY] * Math::lerp(1.0f, float(Math::randf()), randomness[PARAM_INITIAL_LINEAR_VELOCITY]); + p.velocity = rot * Math::lerp(parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], parameters_max[PARAM_INITIAL_LINEAR_VELOCITY], Math::randf()); } else { //initiate velocity spread in 3D - float angle1_rad = Math::atan2(direction.x, direction.z) + Math::deg2rad((Math::randf() * 2.0 - 1.0) * spread); - float angle2_rad = Math::atan2(direction.y, Math::abs(direction.z)) + Math::deg2rad((Math::randf() * 2.0 - 1.0) * (1.0 - flatness) * spread); + real_t angle1_rad = Math::deg2rad((Math::randf() * (real_t)2.0 - (real_t)1.0) * spread); + real_t angle2_rad = Math::deg2rad((Math::randf() * (real_t)2.0 - (real_t)1.0) * ((real_t)1.0 - flatness) * spread); Vector3 direction_xz = Vector3(Math::sin(angle1_rad), 0, Math::cos(angle1_rad)); Vector3 direction_yz = Vector3(0, Math::sin(angle2_rad), Math::cos(angle2_rad)); - direction_yz.z = direction_yz.z / MAX(0.0001, Math::sqrt(ABS(direction_yz.z))); //better uniform distribution - Vector3 direction = Vector3(direction_xz.x * direction_yz.z, direction_yz.y, direction_xz.z * direction_yz.z); - direction.normalize(); - p.velocity = direction * parameters[PARAM_INITIAL_LINEAR_VELOCITY] * Math::lerp(1.0f, float(Math::randf()), randomness[PARAM_INITIAL_LINEAR_VELOCITY]); + Vector3 spread_direction = Vector3(direction_xz.x * direction_yz.z, direction_yz.y, direction_xz.z * direction_yz.z); + Vector3 direction_nrm = direction; + if (direction_nrm.length_squared() > 0) { + direction_nrm.normalize(); + } else { + direction_nrm = Vector3(0, 0, 1); + } + // rotate spread to direction + Vector3 binormal = Vector3(0.0, 1.0, 0.0).cross(direction_nrm); + if (binormal.length_squared() < 0.00000001) { + // direction is parallel to Y. Choose Z as the binormal. + binormal = Vector3(0.0, 0.0, 1.0); + } + binormal.normalize(); + Vector3 normal = binormal.cross(direction_nrm); + spread_direction = binormal * spread_direction.x + normal * spread_direction.y + direction_nrm * spread_direction.z; + p.velocity = spread_direction * Math::lerp(parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], parameters_max[PARAM_INITIAL_LINEAR_VELOCITY], float(Math::randf())); } - float base_angle = (parameters[PARAM_ANGLE] + tex_angle) * Math::lerp(1.0f, p.angle_rand, randomness[PARAM_ANGLE]); + real_t base_angle = tex_angle * Math::lerp(parameters_min[PARAM_ANGLE], parameters_max[PARAM_ANGLE], p.angle_rand); p.custom[0] = Math::deg2rad(base_angle); //angle p.custom[1] = 0.0; //phase - p.custom[2] = (parameters[PARAM_ANIM_OFFSET] + tex_anim_offset) * Math::lerp(1.0f, p.anim_offset_rand, randomness[PARAM_ANIM_OFFSET]); //animation offset (0-1) + p.custom[2] = tex_anim_offset * Math::lerp(parameters_min[PARAM_ANIM_OFFSET], parameters_max[PARAM_ANIM_OFFSET], p.anim_offset_rand); //animation offset (0-1) p.transform = Transform3D(); p.time = 0; p.lifetime = lifetime * (1.0 - Math::randf() * lifetime_randomness); @@ -750,6 +837,21 @@ void CPUParticles3D::_particles_process(float p_delta) { p.base_color = emission_colors.get(random_idx); } } break; + case EMISSION_SHAPE_RING: { + real_t ring_random_angle = Math::randf() * Math_TAU; + real_t ring_random_radius = Math::randf() * (emission_ring_radius - emission_ring_inner_radius) + emission_ring_inner_radius; + Vector3 axis = emission_ring_axis.normalized(); + Vector3 ortho_axis = Vector3(); + if (axis == Vector3(1.0, 0.0, 0.0)) { + ortho_axis = Vector3(0.0, 1.0, 0.0).cross(axis); + } else { + ortho_axis = Vector3(1.0, 0.0, 0.0).cross(axis); + } + ortho_axis = ortho_axis.normalized(); + ortho_axis.rotate(axis, ring_random_angle); + ortho_axis = ortho_axis.normalized(); + p.transform.origin = ortho_axis * ring_random_radius + (Math::randf() * emission_ring_height - emission_ring_height / 2.0) * axis; + } break; case EMISSION_SHAPE_MAX: { // Max value for validity check. break; } @@ -777,53 +879,53 @@ void CPUParticles3D::_particles_process(float p_delta) { p.custom[1] = p.time / lifetime; tv = p.time / p.lifetime; - float tex_linear_velocity = 0.0; + real_t tex_linear_velocity = 0.0; if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { tex_linear_velocity = curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY]->interpolate(tv); } - float tex_orbit_velocity = 0.0; + real_t tex_orbit_velocity = 0.0; if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { if (curve_parameters[PARAM_ORBIT_VELOCITY].is_valid()) { tex_orbit_velocity = curve_parameters[PARAM_ORBIT_VELOCITY]->interpolate(tv); } } - float tex_angular_velocity = 0.0; + real_t tex_angular_velocity = 0.0; if (curve_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) { tex_angular_velocity = curve_parameters[PARAM_ANGULAR_VELOCITY]->interpolate(tv); } - float tex_linear_accel = 0.0; + real_t tex_linear_accel = 0.0; if (curve_parameters[PARAM_LINEAR_ACCEL].is_valid()) { tex_linear_accel = curve_parameters[PARAM_LINEAR_ACCEL]->interpolate(tv); } - float tex_tangential_accel = 0.0; + real_t tex_tangential_accel = 0.0; if (curve_parameters[PARAM_TANGENTIAL_ACCEL].is_valid()) { tex_tangential_accel = curve_parameters[PARAM_TANGENTIAL_ACCEL]->interpolate(tv); } - float tex_radial_accel = 0.0; + real_t tex_radial_accel = 0.0; if (curve_parameters[PARAM_RADIAL_ACCEL].is_valid()) { tex_radial_accel = curve_parameters[PARAM_RADIAL_ACCEL]->interpolate(tv); } - float tex_damping = 0.0; + real_t tex_damping = 0.0; if (curve_parameters[PARAM_DAMPING].is_valid()) { tex_damping = curve_parameters[PARAM_DAMPING]->interpolate(tv); } - float tex_angle = 0.0; + real_t tex_angle = 0.0; if (curve_parameters[PARAM_ANGLE].is_valid()) { tex_angle = curve_parameters[PARAM_ANGLE]->interpolate(tv); } - float tex_anim_speed = 0.0; + real_t tex_anim_speed = 0.0; if (curve_parameters[PARAM_ANIM_SPEED].is_valid()) { tex_anim_speed = curve_parameters[PARAM_ANIM_SPEED]->interpolate(tv); } - float tex_anim_offset = 0.0; + real_t tex_anim_offset = 0.0; if (curve_parameters[PARAM_ANIM_OFFSET].is_valid()) { tex_anim_offset = curve_parameters[PARAM_ANIM_OFFSET]->interpolate(tv); } @@ -834,28 +936,27 @@ void CPUParticles3D::_particles_process(float p_delta) { position.z = 0.0; } //apply linear acceleration - force += p.velocity.length() > 0.0 ? p.velocity.normalized() * (parameters[PARAM_LINEAR_ACCEL] + tex_linear_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_LINEAR_ACCEL]) : Vector3(); + force += p.velocity.length() > 0.0 ? p.velocity.normalized() * tex_linear_accel * Math::lerp(parameters_min[PARAM_LINEAR_ACCEL], parameters_max[PARAM_LINEAR_ACCEL], rand_from_seed(alt_seed)) : Vector3(); //apply radial acceleration Vector3 org = emission_xform.origin; Vector3 diff = position - org; - force += diff.length() > 0.0 ? diff.normalized() * (parameters[PARAM_RADIAL_ACCEL] + tex_radial_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_RADIAL_ACCEL]) : Vector3(); - //apply tangential acceleration; + force += diff.length() > 0.0 ? diff.normalized() * (tex_radial_accel)*Math::lerp(parameters_min[PARAM_RADIAL_ACCEL], parameters_max[PARAM_RADIAL_ACCEL], rand_from_seed(alt_seed)) : Vector3(); if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { Vector2 yx = Vector2(diff.y, diff.x); Vector2 yx2 = (yx * Vector2(-1.0, 1.0)).normalized(); - force += yx.length() > 0.0 ? Vector3(yx2.x, yx2.y, 0.0) * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector3(); + force += yx.length() > 0.0 ? Vector3(yx2.x, yx2.y, 0.0) * (tex_tangential_accel * Math::lerp(parameters_min[PARAM_TANGENTIAL_ACCEL], parameters_max[PARAM_TANGENTIAL_ACCEL], rand_from_seed(alt_seed))) : Vector3(); } else { Vector3 crossDiff = diff.normalized().cross(gravity.normalized()); - force += crossDiff.length() > 0.0 ? crossDiff.normalized() * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector3(); + force += crossDiff.length() > 0.0 ? crossDiff.normalized() * (tex_tangential_accel * Math::lerp(parameters_min[PARAM_TANGENTIAL_ACCEL], parameters_max[PARAM_TANGENTIAL_ACCEL], rand_from_seed(alt_seed))) : Vector3(); } //apply attractor forces p.velocity += force * local_delta; //orbit velocity if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { - float orbit_amount = (parameters[PARAM_ORBIT_VELOCITY] + tex_orbit_velocity) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_ORBIT_VELOCITY]); + real_t orbit_amount = tex_orbit_velocity * Math::lerp(parameters_min[PARAM_ORBIT_VELOCITY], parameters_max[PARAM_ORBIT_VELOCITY], rand_from_seed(alt_seed)); if (orbit_amount != 0.0) { - float ang = orbit_amount * local_delta * Math_TAU; + real_t ang = orbit_amount * local_delta * Math_TAU; // Not sure why the ParticlesMaterial code uses a clockwise rotation matrix, // but we use -ang here to reproduce its behavior. Transform2D rot = Transform2D(-ang, Vector2()); @@ -867,9 +968,10 @@ void CPUParticles3D::_particles_process(float p_delta) { if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { p.velocity = p.velocity.normalized() * tex_linear_velocity; } - if (parameters[PARAM_DAMPING] + tex_damping > 0.0) { - float v = p.velocity.length(); - float damp = (parameters[PARAM_DAMPING] + tex_damping) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_DAMPING]); + + if (parameters_max[PARAM_DAMPING] + tex_damping > 0.0) { + real_t v = p.velocity.length(); + real_t damp = tex_damping * Math::lerp(parameters_min[PARAM_DAMPING], parameters_max[PARAM_DAMPING], rand_from_seed(alt_seed)); v -= damp * local_delta; if (v < 0.0) { p.velocity = Vector3(); @@ -877,27 +979,48 @@ void CPUParticles3D::_particles_process(float p_delta) { p.velocity = p.velocity.normalized() * v; } } - float base_angle = (parameters[PARAM_ANGLE] + tex_angle) * Math::lerp(1.0f, p.angle_rand, randomness[PARAM_ANGLE]); - base_angle += p.custom[1] * lifetime * (parameters[PARAM_ANGULAR_VELOCITY] + tex_angular_velocity) * Math::lerp(1.0f, rand_from_seed(alt_seed) * 2.0f - 1.0f, randomness[PARAM_ANGULAR_VELOCITY]); + real_t base_angle = (tex_angle)*Math::lerp(parameters_min[PARAM_ANGLE], parameters_max[PARAM_ANGLE], p.angle_rand); + base_angle += p.custom[1] * lifetime * tex_angular_velocity * Math::lerp(parameters_min[PARAM_ANGULAR_VELOCITY], parameters_max[PARAM_ANGULAR_VELOCITY], rand_from_seed(alt_seed)); p.custom[0] = Math::deg2rad(base_angle); //angle - p.custom[2] = (parameters[PARAM_ANIM_OFFSET] + tex_anim_offset) * Math::lerp(1.0f, p.anim_offset_rand, randomness[PARAM_ANIM_OFFSET]) + p.custom[1] * (parameters[PARAM_ANIM_SPEED] + tex_anim_speed) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_ANIM_SPEED]); //angle + p.custom[2] = tex_anim_offset * Math::lerp(parameters_min[PARAM_ANIM_OFFSET], parameters_max[PARAM_ANIM_OFFSET], p.anim_offset_rand) + p.custom[1] * tex_anim_speed * Math::lerp(parameters_min[PARAM_ANIM_SPEED], parameters_max[PARAM_ANIM_SPEED], rand_from_seed(alt_seed)); //angle } //apply color //apply hue rotation - float tex_scale = 1.0; - if (curve_parameters[PARAM_SCALE].is_valid()) { - tex_scale = curve_parameters[PARAM_SCALE]->interpolate(tv); + Vector3 tex_scale = Vector3(1.0, 1.0, 1.0); + if (split_scale) { + if (scale_curve_x.is_valid()) { + tex_scale.x = scale_curve_x->interpolate(tv); + } else { + tex_scale.x = 1.0; + } + if (scale_curve_y.is_valid()) { + tex_scale.y = scale_curve_y->interpolate(tv); + } else { + tex_scale.y = 1.0; + } + if (scale_curve_z.is_valid()) { + tex_scale.z = scale_curve_z->interpolate(tv); + } else { + tex_scale.z = 1.0; + } + } else { + if (curve_parameters[PARAM_SCALE].is_valid()) { + float tmp_scale = curve_parameters[PARAM_SCALE]->interpolate(tv); + tex_scale.x = tmp_scale; + tex_scale.y = tmp_scale; + tex_scale.z = tmp_scale; + } } - float tex_hue_variation = 0.0; + real_t tex_hue_variation = 0.0; if (curve_parameters[PARAM_HUE_VARIATION].is_valid()) { tex_hue_variation = curve_parameters[PARAM_HUE_VARIATION]->interpolate(tv); } - float hue_rot_angle = (parameters[PARAM_HUE_VARIATION] + tex_hue_variation) * Math_TAU * Math::lerp(1.0f, p.hue_rot_rand * 2.0f - 1.0f, randomness[PARAM_HUE_VARIATION]); - float hue_rot_c = Math::cos(hue_rot_angle); - float hue_rot_s = Math::sin(hue_rot_angle); + real_t hue_rot_angle = (tex_hue_variation)*Math_TAU * Math::lerp(parameters_min[PARAM_HUE_VARIATION], parameters_max[PARAM_HUE_VARIATION], p.hue_rot_rand); + real_t hue_rot_c = Math::cos(hue_rot_angle); + real_t hue_rot_s = Math::sin(hue_rot_angle); Basis hue_rot_mat; { @@ -965,13 +1088,21 @@ void CPUParticles3D::_particles_process(float p_delta) { } } + p.transform.basis = p.transform.basis.orthonormalized(); //scale by scale - float base_scale = tex_scale * Math::lerp(parameters[PARAM_SCALE], 1.0f, p.scale_rand * randomness[PARAM_SCALE]); - if (base_scale < 0.000001) { - base_scale = 0.000001; + + Vector3 base_scale = tex_scale * Math::lerp(parameters_min[PARAM_SCALE], parameters_max[PARAM_SCALE], p.scale_rand); + if (base_scale.x < CMP_EPSILON) { + base_scale.x = CMP_EPSILON; + } + if (base_scale.y < CMP_EPSILON) { + base_scale.y = CMP_EPSILON; + } + if (base_scale.z < CMP_EPSILON) { + base_scale.z = CMP_EPSILON; } - p.transform.basis.scale(Vector3(1, 1, 1) * base_scale); + p.transform.basis.scale(base_scale); if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { p.velocity.z = 0.0; @@ -1007,7 +1138,7 @@ void CPUParticles3D::_update_particle_data_buffer() { sorter.sort(order, pc); } else if (draw_order == DRAW_ORDER_VIEW_DEPTH) { ERR_FAIL_NULL(get_viewport()); - Camera3D *c = get_viewport()->get_camera(); + Camera3D *c = get_viewport()->get_camera_3d(); if (c) { Vector3 dir = c->get_global_transform().basis.get_axis(2); //far away to close @@ -1050,7 +1181,7 @@ void CPUParticles3D::_update_particle_data_buffer() { ptr[10] = t.basis.elements[2][2]; ptr[11] = t.origin.z; } else { - memset(ptr, 0, sizeof(float) * 12); + memset(ptr, 0, sizeof(Transform3D)); } Color c = r[idx].color; @@ -1207,18 +1338,25 @@ void CPUParticles3D::convert_from_particles(Node *p_particles) { set_emission_shape(EmissionShape(material->get_emission_shape())); set_emission_sphere_radius(material->get_emission_sphere_radius()); set_emission_box_extents(material->get_emission_box_extents()); + Ref<CurveXYZTexture> scale3D = material->get_param_texture(ParticlesMaterial::PARAM_SCALE); + if (scale3D.is_valid()) { + split_scale = true; + scale_curve_x = scale3D->get_curve_x(); + scale_curve_y = scale3D->get_curve_y(); + scale_curve_z = scale3D->get_curve_z(); + } set_gravity(material->get_gravity()); set_lifetime_randomness(material->get_lifetime_randomness()); #define CONVERT_PARAM(m_param) \ - set_param(m_param, material->get_param(ParticlesMaterial::m_param)); \ + set_param_min(m_param, material->get_param_min(ParticlesMaterial::m_param)); \ { \ Ref<CurveTexture> ctex = material->get_param_texture(ParticlesMaterial::m_param); \ if (ctex.is_valid()) \ set_param_curve(m_param, ctex->get_curve()); \ } \ - set_param_randomness(m_param, material->get_param_randomness(ParticlesMaterial::m_param)); + set_param_max(m_param, material->get_param_max(ParticlesMaterial::m_param)); CONVERT_PARAM(PARAM_INITIAL_LINEAR_VELOCITY); CONVERT_PARAM(PARAM_ANGULAR_VELOCITY); @@ -1304,11 +1442,11 @@ void CPUParticles3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_flatness", "amount"), &CPUParticles3D::set_flatness); ClassDB::bind_method(D_METHOD("get_flatness"), &CPUParticles3D::get_flatness); - ClassDB::bind_method(D_METHOD("set_param", "param", "value"), &CPUParticles3D::set_param); - ClassDB::bind_method(D_METHOD("get_param", "param"), &CPUParticles3D::get_param); + ClassDB::bind_method(D_METHOD("set_param_min", "param", "value"), &CPUParticles3D::set_param_min); + ClassDB::bind_method(D_METHOD("get_param_min", "param"), &CPUParticles3D::get_param_min); - ClassDB::bind_method(D_METHOD("set_param_randomness", "param", "randomness"), &CPUParticles3D::set_param_randomness); - ClassDB::bind_method(D_METHOD("get_param_randomness", "param"), &CPUParticles3D::get_param_randomness); + ClassDB::bind_method(D_METHOD("set_param_max", "param", "value"), &CPUParticles3D::set_param_max); + ClassDB::bind_method(D_METHOD("get_param_max", "param"), &CPUParticles3D::get_param_max); ClassDB::bind_method(D_METHOD("set_param_curve", "param", "curve"), &CPUParticles3D::set_param_curve); ClassDB::bind_method(D_METHOD("get_param_curve", "param"), &CPUParticles3D::get_param_curve); @@ -1340,18 +1478,46 @@ void CPUParticles3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_emission_colors", "array"), &CPUParticles3D::set_emission_colors); ClassDB::bind_method(D_METHOD("get_emission_colors"), &CPUParticles3D::get_emission_colors); + ClassDB::bind_method(D_METHOD("set_emission_ring_axis", "axis"), &CPUParticles3D::set_emission_ring_axis); + ClassDB::bind_method(D_METHOD("get_emission_ring_axis"), &CPUParticles3D::get_emission_ring_axis); + + ClassDB::bind_method(D_METHOD("set_emission_ring_height", "height"), &CPUParticles3D::set_emission_ring_height); + ClassDB::bind_method(D_METHOD("get_emission_ring_height"), &CPUParticles3D::get_emission_ring_height); + + ClassDB::bind_method(D_METHOD("set_emission_ring_radius", "radius"), &CPUParticles3D::set_emission_ring_radius); + ClassDB::bind_method(D_METHOD("get_emission_ring_radius"), &CPUParticles3D::get_emission_ring_radius); + + ClassDB::bind_method(D_METHOD("set_emission_ring_inner_radius", "inner_radius"), &CPUParticles3D::set_emission_ring_inner_radius); + ClassDB::bind_method(D_METHOD("get_emission_ring_inner_radius"), &CPUParticles3D::get_emission_ring_inner_radius); + ClassDB::bind_method(D_METHOD("get_gravity"), &CPUParticles3D::get_gravity); ClassDB::bind_method(D_METHOD("set_gravity", "accel_vec"), &CPUParticles3D::set_gravity); + ClassDB::bind_method(D_METHOD("get_split_scale"), &CPUParticles3D::get_split_scale); + ClassDB::bind_method(D_METHOD("set_split_scale", "split_scale"), &CPUParticles3D::set_split_scale); + + ClassDB::bind_method(D_METHOD("get_scale_curve_x"), &CPUParticles3D::get_scale_curve_x); + ClassDB::bind_method(D_METHOD("set_scale_curve_x", "scale_curve"), &CPUParticles3D::set_scale_curve_x); + + ClassDB::bind_method(D_METHOD("get_scale_curve_y"), &CPUParticles3D::get_scale_curve_y); + ClassDB::bind_method(D_METHOD("set_scale_curve_y", "scale_curve"), &CPUParticles3D::set_scale_curve_y); + + ClassDB::bind_method(D_METHOD("get_scale_curve_z"), &CPUParticles3D::get_scale_curve_z); + ClassDB::bind_method(D_METHOD("set_scale_curve_z", "scale_curve"), &CPUParticles3D::set_scale_curve_z); + ClassDB::bind_method(D_METHOD("convert_from_particles", "particles"), &CPUParticles3D::convert_from_particles); ADD_GROUP("Emission Shape", "emission_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_shape", PROPERTY_HINT_ENUM, "Point,Sphere,Box,Points,Directed Points", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_emission_shape", "get_emission_shape"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_shape", PROPERTY_HINT_ENUM, "Point,Sphere,Box,Points,Directed Points,Ring", 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::VECTOR3, "emission_box_extents"), "set_emission_box_extents", "get_emission_box_extents"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "emission_points"), "set_emission_points", "get_emission_points"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "emission_normals"), "set_emission_normals", "get_emission_normals"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "emission_colors"), "set_emission_colors", "get_emission_colors"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_ring_axis"), "set_emission_ring_axis", "get_emission_ring_axis"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height"), "set_emission_ring_height", "get_emission_ring_height"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius"), "set_emission_ring_radius", "get_emission_ring_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius"); ADD_GROUP("Particle Flags", "particle_flag_"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_align_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_rotate_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ROTATE_Y); @@ -1363,54 +1529,58 @@ void CPUParticles3D::_bind_methods() { ADD_GROUP("Gravity", ""); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "gravity"), "set_gravity", "get_gravity"); ADD_GROUP("Initial Velocity", "initial_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param", "get_param", PARAM_INITIAL_LINEAR_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_INITIAL_LINEAR_VELOCITY); + 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_GROUP("Angular Velocity", "angular_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ANGULAR_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGULAR_VELOCITY); + 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); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angular_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANGULAR_VELOCITY); ADD_GROUP("Orbit Velocity", "orbit_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ORBIT_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ORBIT_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_min", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_ORBIT_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_max", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_ORBIT_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "orbit_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ORBIT_VELOCITY); ADD_GROUP("Linear Accel", "linear_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_LINEAR_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_LINEAR_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_LINEAR_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_LINEAR_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "linear_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_LINEAR_ACCEL); ADD_GROUP("Radial Accel", "radial_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_RADIAL_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_RADIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_RADIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_RADIAL_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "radial_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_RADIAL_ACCEL); ADD_GROUP("Tangential Accel", "tangential_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_TANGENTIAL_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_TANGENTIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_TANGENTIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_TANGENTIAL_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "tangential_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_TANGENTIAL_ACCEL); ADD_GROUP("Damping", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_param", "get_param", PARAM_DAMPING); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_min", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_param_min", "get_param_min", PARAM_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_max", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_param_max", "get_param_max", PARAM_DAMPING); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_DAMPING); ADD_GROUP("Angle", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param", "get_param", PARAM_ANGLE); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGLE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_min", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param_min", "get_param_min", PARAM_ANGLE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_max", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param_max", "get_param_max", PARAM_ANGLE); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angle_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANGLE); ADD_GROUP("Scale", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_amount", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param", "get_param", PARAM_SCALE); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_amount_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_SCALE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_amount_min", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_min", "get_param_min", PARAM_SCALE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_amount_max", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_max", "get_param_max", PARAM_SCALE); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "scale_amount_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_SCALE); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "split_scale"), "set_split_scale", "get_split_scale"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "scale_curve_x", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_scale_curve_x", "get_scale_curve_x"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "scale_curve_y", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_scale_curve_y", "get_scale_curve_y"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "scale_curve_z", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_scale_curve_z", "get_scale_curve_z"); ADD_GROUP("Color", ""); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "color_ramp", PROPERTY_HINT_RESOURCE_TYPE, "Gradient"), "set_color_ramp", "get_color_ramp"); ADD_GROUP("Hue Variation", "hue_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param", "get_param", PARAM_HUE_VARIATION); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_HUE_VARIATION); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_min", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param_min", "get_param_min", PARAM_HUE_VARIATION); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_max", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param_max", "get_param_max", PARAM_HUE_VARIATION); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "hue_variation_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_HUE_VARIATION); ADD_GROUP("Animation", "anim_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater"), "set_param", "get_param", PARAM_ANIM_SPEED); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANIM_SPEED); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_min", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater,or_lesser"), "set_param_min", "get_param_min", PARAM_ANIM_SPEED); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_max", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater,or_lesser"), "set_param_max", "get_param_max", PARAM_ANIM_SPEED); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_speed_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANIM_SPEED); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_ANIM_OFFSET); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANIM_OFFSET); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_min", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_min", "get_param_min", PARAM_ANIM_OFFSET); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_max", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_max", "get_param_max", PARAM_ANIM_OFFSET); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_offset_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANIM_OFFSET); BIND_ENUM_CONSTANT(PARAM_INITIAL_LINEAR_VELOCITY); @@ -1437,6 +1607,7 @@ void CPUParticles3D::_bind_methods() { BIND_ENUM_CONSTANT(EMISSION_SHAPE_BOX); BIND_ENUM_CONSTANT(EMISSION_SHAPE_POINTS); BIND_ENUM_CONSTANT(EMISSION_SHAPE_DIRECTED_POINTS); + BIND_ENUM_CONSTANT(EMISSION_SHAPE_RING); BIND_ENUM_CONSTANT(EMISSION_SHAPE_MAX); } @@ -1450,28 +1621,40 @@ CPUParticles3D::CPUParticles3D() { set_emitting(true); set_amount(8); - set_param(PARAM_INITIAL_LINEAR_VELOCITY, 0); - set_param(PARAM_ANGULAR_VELOCITY, 0); - set_param(PARAM_ORBIT_VELOCITY, 0); - set_param(PARAM_LINEAR_ACCEL, 0); - set_param(PARAM_RADIAL_ACCEL, 0); - set_param(PARAM_TANGENTIAL_ACCEL, 0); - set_param(PARAM_DAMPING, 0); - set_param(PARAM_ANGLE, 0); - set_param(PARAM_SCALE, 1); - set_param(PARAM_HUE_VARIATION, 0); - set_param(PARAM_ANIM_SPEED, 0); - set_param(PARAM_ANIM_OFFSET, 0); + set_param_min(PARAM_INITIAL_LINEAR_VELOCITY, 0); + set_param_min(PARAM_ANGULAR_VELOCITY, 0); + set_param_min(PARAM_ORBIT_VELOCITY, 0); + set_param_min(PARAM_LINEAR_ACCEL, 0); + set_param_min(PARAM_RADIAL_ACCEL, 0); + set_param_min(PARAM_TANGENTIAL_ACCEL, 0); + set_param_min(PARAM_DAMPING, 0); + set_param_min(PARAM_ANGLE, 0); + set_param_min(PARAM_SCALE, 1); + set_param_min(PARAM_HUE_VARIATION, 0); + set_param_min(PARAM_ANIM_SPEED, 0); + set_param_min(PARAM_ANIM_OFFSET, 0); + set_param_max(PARAM_INITIAL_LINEAR_VELOCITY, 0); + set_param_max(PARAM_ANGULAR_VELOCITY, 0); + set_param_max(PARAM_ORBIT_VELOCITY, 0); + set_param_max(PARAM_LINEAR_ACCEL, 0); + set_param_max(PARAM_RADIAL_ACCEL, 0); + set_param_max(PARAM_TANGENTIAL_ACCEL, 0); + set_param_max(PARAM_DAMPING, 0); + set_param_max(PARAM_ANGLE, 0); + set_param_max(PARAM_SCALE, 1); + set_param_max(PARAM_HUE_VARIATION, 0); + set_param_max(PARAM_ANIM_SPEED, 0); + set_param_max(PARAM_ANIM_OFFSET, 0); set_emission_shape(EMISSION_SHAPE_POINT); set_emission_sphere_radius(1); set_emission_box_extents(Vector3(1, 1, 1)); + set_emission_ring_axis(Vector3(0, 0, 1.0)); + set_emission_ring_height(1); + set_emission_ring_radius(1); + set_emission_ring_inner_radius(0); set_gravity(Vector3(0, -9.8, 0)); - for (int i = 0; i < PARAM_MAX; i++) { - set_param_randomness(Parameter(i), 0); - } - for (int i = 0; i < PARTICLE_FLAG_MAX; i++) { particle_flags[i] = false; } diff --git a/scene/3d/cpu_particles_3d.h b/scene/3d/cpu_particles_3d.h index b35e659757..160814ead4 100644 --- a/scene/3d/cpu_particles_3d.h +++ b/scene/3d/cpu_particles_3d.h @@ -31,8 +31,6 @@ #ifndef CPU_PARTICLES_H #define CPU_PARTICLES_H -#include "core/templates/rid.h" -#include "core/templates/safe_refcount.h" #include "scene/3d/visual_instance_3d.h" class CPUParticles3D : public GeometryInstance3D { @@ -76,6 +74,7 @@ public: EMISSION_SHAPE_BOX, EMISSION_SHAPE_POINTS, EMISSION_SHAPE_DIRECTED_POINTS, + EMISSION_SHAPE_RING, EMISSION_SHAPE_MAX }; @@ -85,23 +84,23 @@ private: struct Particle { Transform3D transform; Color color; - float custom[4] = {}; + real_t custom[4] = {}; Vector3 velocity; bool active = false; - float angle_rand = 0.0; - float scale_rand = 0.0; - float hue_rot_rand = 0.0; - float anim_offset_rand = 0.0; - float time = 0.0; - float lifetime = 0.0; + real_t angle_rand = 0.0; + real_t scale_rand = 0.0; + real_t hue_rot_rand = 0.0; + real_t anim_offset_rand = 0.0; + double time = 0.0; + double lifetime = 0.0; Color base_color; uint32_t seed = 0; }; - float time = 0.0; - float inactive_time = 0.0; - float frame_remainder = 0.0; + double time = 0.0; + double inactive_time = 0.0; + double frame_remainder = 0.0; int cycle = 0; bool redraw = false; @@ -131,12 +130,12 @@ private: bool one_shot = false; - float lifetime = 1.0; - float pre_process_time = 0.0; - float explosiveness_ratio = 0.0; - float randomness_ratio = 0.0; - float lifetime_randomness = 0.0; - float speed_scale = 1.0; + double lifetime = 1.0; + double pre_process_time = 0.0; + real_t explosiveness_ratio = 0.0; + real_t randomness_ratio = 0.0; + double lifetime_randomness = 0.0; + double speed_scale = 1.0; bool local_coords = true; int fixed_fps = 0; bool fractional_delta = true; @@ -152,11 +151,11 @@ private: //////// Vector3 direction = Vector3(1, 0, 0); - float spread = 45.0; - float flatness = 0.0; + real_t spread = 45.0; + real_t flatness = 0.0; - float parameters[PARAM_MAX]; - float randomness[PARAM_MAX] = {}; + real_t parameters_min[PARAM_MAX]; + real_t parameters_max[PARAM_MAX] = {}; Ref<Curve> curve_parameters[PARAM_MAX]; Color color = Color(1, 1, 1, 1); @@ -165,17 +164,26 @@ private: bool particle_flags[PARTICLE_FLAG_MAX] = {}; EmissionShape emission_shape = EMISSION_SHAPE_POINT; - float emission_sphere_radius = 1.0; + real_t emission_sphere_radius = 1.0; Vector3 emission_box_extents = Vector3(1, 1, 1); Vector<Vector3> emission_points; Vector<Vector3> emission_normals; 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; + + Ref<Curve> scale_curve_x; + Ref<Curve> scale_curve_y; + Ref<Curve> scale_curve_z; + bool split_scale = false; Vector3 gravity = Vector3(0, -9.8, 0); void _update_internal(); - void _particles_process(float p_delta); + void _particles_process(double p_delta); void _update_particle_data_buffer(); Mutex update_mutex; @@ -195,27 +203,27 @@ public: void set_emitting(bool p_emitting); void set_amount(int p_amount); - void set_lifetime(float p_lifetime); + void set_lifetime(double p_lifetime); void set_one_shot(bool p_one_shot); - void set_pre_process_time(float p_time); - void set_explosiveness_ratio(float p_ratio); - void set_randomness_ratio(float p_ratio); - void set_lifetime_randomness(float p_random); + void set_pre_process_time(double p_time); + void set_explosiveness_ratio(real_t p_ratio); + void set_randomness_ratio(real_t p_ratio); + void set_lifetime_randomness(double p_random); void set_visibility_aabb(const AABB &p_aabb); void set_use_local_coordinates(bool p_enable); - void set_speed_scale(float p_scale); + void set_speed_scale(double p_scale); bool is_emitting() const; int get_amount() const; - float get_lifetime() const; + double get_lifetime() const; bool get_one_shot() const; - float get_pre_process_time() const; - float get_explosiveness_ratio() const; - float get_randomness_ratio() const; - float get_lifetime_randomness() const; + double get_pre_process_time() const; + real_t get_explosiveness_ratio() const; + real_t get_randomness_ratio() const; + double get_lifetime_randomness() const; AABB get_visibility_aabb() const; bool get_use_local_coordinates() const; - float get_speed_scale() const; + double get_speed_scale() const; void set_fixed_fps(int p_count); int get_fixed_fps() const; @@ -237,17 +245,17 @@ public: void set_direction(Vector3 p_direction); Vector3 get_direction() const; - void set_spread(float p_spread); - float get_spread() const; + void set_spread(real_t p_spread); + real_t get_spread() const; - void set_flatness(float p_flatness); - float get_flatness() const; + void set_flatness(real_t p_flatness); + real_t get_flatness() const; - void set_param(Parameter p_param, float p_value); - float get_param(Parameter p_param) const; + void set_param_min(Parameter p_param, real_t p_value); + real_t get_param_min(Parameter p_param) const; - void set_param_randomness(Parameter p_param, float p_value); - float get_param_randomness(Parameter p_param) const; + void set_param_max(Parameter p_param, real_t p_value); + real_t get_param_max(Parameter p_param) const; void set_param_curve(Parameter p_param, const Ref<Curve> &p_curve); Ref<Curve> get_param_curve(Parameter p_param) const; @@ -262,20 +270,36 @@ public: bool get_particle_flag(ParticleFlags p_particle_flag) const; void set_emission_shape(EmissionShape p_shape); - void set_emission_sphere_radius(float p_radius); + void set_emission_sphere_radius(real_t p_radius); void set_emission_box_extents(Vector3 p_extents); void set_emission_points(const Vector<Vector3> &p_points); void set_emission_normals(const Vector<Vector3> &p_normals); void set_emission_colors(const Vector<Color> &p_colors); void set_emission_point_count(int p_count); + void set_emission_ring_axis(Vector3 p_axis); + void set_emission_ring_height(real_t p_height); + void set_emission_ring_radius(real_t p_radius); + void set_emission_ring_inner_radius(real_t p_radius); + void set_scale_curve_x(Ref<Curve> p_scale_curve); + void set_scale_curve_y(Ref<Curve> p_scale_curve); + void set_scale_curve_z(Ref<Curve> p_scale_curve); + void set_split_scale(bool p_split_scale); EmissionShape get_emission_shape() const; - float get_emission_sphere_radius() const; + real_t get_emission_sphere_radius() const; Vector3 get_emission_box_extents() const; Vector<Vector3> get_emission_points() const; Vector<Vector3> get_emission_normals() const; Vector<Color> get_emission_colors() const; int get_emission_point_count() const; + Vector3 get_emission_ring_axis() const; + real_t get_emission_ring_height() const; + real_t get_emission_ring_radius() const; + real_t get_emission_ring_inner_radius() const; + Ref<Curve> get_scale_curve_x() const; + Ref<Curve> get_scale_curve_y() const; + Ref<Curve> get_scale_curve_z() const; + bool get_split_scale(); void set_gravity(const Vector3 &p_gravity); Vector3 get_gravity() const; diff --git a/scene/3d/decal.cpp b/scene/3d/decal.cpp index 7d6abe458a..c94a99a203 100644 --- a/scene/3d/decal.cpp +++ b/scene/3d/decal.cpp @@ -33,7 +33,7 @@ void Decal::set_extents(const Vector3 &p_extents) { extents = p_extents; RS::get_singleton()->decal_set_extents(decal, p_extents); - update_gizmo(); + update_gizmos(); } Vector3 Decal::get_extents() const { @@ -45,6 +45,7 @@ void Decal::set_texture(DecalTexture p_type, const Ref<Texture2D> &p_texture) { textures[p_type] = p_texture; RID texture_rid = p_texture.is_valid() ? p_texture->get_rid() : RID(); RS::get_singleton()->decal_set_texture(decal, RS::DecalTexture(p_type), texture_rid); + update_configuration_warnings(); } Ref<Texture2D> Decal::get_texture(DecalTexture p_type) const { @@ -52,48 +53,48 @@ Ref<Texture2D> Decal::get_texture(DecalTexture p_type) const { return textures[p_type]; } -void Decal::set_emission_energy(float p_energy) { +void Decal::set_emission_energy(real_t p_energy) { emission_energy = p_energy; RS::get_singleton()->decal_set_emission_energy(decal, emission_energy); } -float Decal::get_emission_energy() const { +real_t Decal::get_emission_energy() const { return emission_energy; } -void Decal::set_albedo_mix(float p_mix) { +void Decal::set_albedo_mix(real_t p_mix) { albedo_mix = p_mix; RS::get_singleton()->decal_set_albedo_mix(decal, albedo_mix); } -float Decal::get_albedo_mix() const { +real_t Decal::get_albedo_mix() const { return albedo_mix; } -void Decal::set_upper_fade(float p_fade) { +void Decal::set_upper_fade(real_t p_fade) { upper_fade = p_fade; RS::get_singleton()->decal_set_fade(decal, upper_fade, lower_fade); } -float Decal::get_upper_fade() const { +real_t Decal::get_upper_fade() const { return upper_fade; } -void Decal::set_lower_fade(float p_fade) { +void Decal::set_lower_fade(real_t p_fade) { lower_fade = p_fade; RS::get_singleton()->decal_set_fade(decal, upper_fade, lower_fade); } -float Decal::get_lower_fade() const { +real_t Decal::get_lower_fade() const { return lower_fade; } -void Decal::set_normal_fade(float p_fade) { +void Decal::set_normal_fade(real_t p_fade) { normal_fade = p_fade; RS::get_singleton()->decal_set_normal_fade(decal, normal_fade); } -float Decal::get_normal_fade() const { +real_t Decal::get_normal_fade() const { return normal_fade; } @@ -116,27 +117,28 @@ bool Decal::is_distance_fade_enabled() const { return distance_fade_enabled; } -void Decal::set_distance_fade_begin(float p_distance) { +void Decal::set_distance_fade_begin(real_t p_distance) { distance_fade_begin = p_distance; RS::get_singleton()->decal_set_distance_fade(decal, distance_fade_enabled, distance_fade_begin, distance_fade_length); } -float Decal::get_distance_fade_begin() const { +real_t Decal::get_distance_fade_begin() const { return distance_fade_begin; } -void Decal::set_distance_fade_length(float p_length) { +void Decal::set_distance_fade_length(real_t p_length) { distance_fade_length = p_length; RS::get_singleton()->decal_set_distance_fade(decal, distance_fade_enabled, distance_fade_begin, distance_fade_length); } -float Decal::get_distance_fade_length() const { +real_t Decal::get_distance_fade_length() const { return distance_fade_length; } void Decal::set_cull_mask(uint32_t p_layers) { cull_mask = p_layers; RS::get_singleton()->decal_set_cull_mask(decal, cull_mask); + update_configuration_warnings(); } uint32_t Decal::get_cull_mask() const { @@ -160,6 +162,27 @@ void Decal::_validate_property(PropertyInfo &property) const { } } +TypedArray<String> Decal::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); + + if (textures[TEXTURE_ALBEDO].is_null() && textures[TEXTURE_NORMAL].is_null() && textures[TEXTURE_ORM].is_null() && textures[TEXTURE_EMISSION].is_null()) { + warnings.push_back(TTR("The decal has no textures loaded into any of its texture properties, and will therefore not be visible.")); + } + + if ((textures[TEXTURE_NORMAL].is_valid() || textures[TEXTURE_ORM].is_valid()) && textures[TEXTURE_ALBEDO].is_null()) { + warnings.push_back(TTR("The decal has a Normal and/or ORM texture, but no Albedo texture is set.\nAn Albedo texture with an alpha channel is required to blend the normal/ORM maps onto the underlying surface.\nIf you don't want the Albedo texture to be visible, set Albedo Mix to 0.")); + } + + if (cull_mask == 0) { + // NOTE: This warning will not be emitted if none of the 20 checkboxes + // exposed in the editor are checked. This is because there are + // currently 12 unexposed layers in the editor inspector. + warnings.push_back(TTR("The decal's Cull Mask has no bits enabled, which means the decal will not paint objects on any layer.\nTo resolve this, enable at least one bit in the Cull Mask property.")); + } + + return warnings; +} + void Decal::_bind_methods() { ClassDB::bind_method(D_METHOD("set_extents", "extents"), &Decal::set_extents); ClassDB::bind_method(D_METHOD("get_extents"), &Decal::get_extents); @@ -207,7 +230,9 @@ void Decal::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_energy", PROPERTY_HINT_RANGE, "0,128,0.01"), "set_emission_energy", "get_emission_energy"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "modulate"), "set_modulate", "get_modulate"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "albedo_mix", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_albedo_mix", "get_albedo_mix"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "normal_fade", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_normal_fade", "get_normal_fade"); + // A Normal Fade of 1.0 causes the decal to be invisible even if fully perpendicular to a surface. + // Due to this, limit Normal Fade to 0.999. + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "normal_fade", PROPERTY_HINT_RANGE, "0,0.999,0.001"), "set_normal_fade", "get_normal_fade"); ADD_GROUP("Vertical Fade", ""); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "upper_fade", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_upper_fade", "get_upper_fade"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lower_fade", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_lower_fade", "get_lower_fade"); diff --git a/scene/3d/decal.h b/scene/3d/decal.h index ce19e76de1..e9bda3276d 100644 --- a/scene/3d/decal.h +++ b/scene/3d/decal.h @@ -32,8 +32,6 @@ #define DECAL_H #include "scene/3d/visual_instance_3d.h" -#include "scene/resources/texture.h" -#include "servers/rendering_server.h" class Decal : public VisualInstance3D { GDCLASS(Decal, VisualInstance3D); @@ -51,54 +49,56 @@ private: RID decal; Vector3 extents = Vector3(1, 1, 1); Ref<Texture2D> textures[TEXTURE_MAX]; - float emission_energy = 1.0; - float albedo_mix = 1.0; + real_t emission_energy = 1.0; + real_t albedo_mix = 1.0; Color modulate = Color(1, 1, 1, 1); uint32_t cull_mask = (1 << 20) - 1; - float normal_fade = 0.0; - float upper_fade = 0.3; - float lower_fade = 0.3; + real_t normal_fade = 0.0; + real_t upper_fade = 0.3; + real_t lower_fade = 0.3; bool distance_fade_enabled = false; - float distance_fade_begin = 10.0; - float distance_fade_length = 1.0; + real_t distance_fade_begin = 10.0; + real_t distance_fade_length = 1.0; protected: static void _bind_methods(); void _validate_property(PropertyInfo &property) const override; public: + virtual TypedArray<String> get_configuration_warnings() const override; + void set_extents(const Vector3 &p_extents); Vector3 get_extents() const; void set_texture(DecalTexture p_type, const Ref<Texture2D> &p_texture); Ref<Texture2D> get_texture(DecalTexture p_type) const; - void set_emission_energy(float p_energy); - float get_emission_energy() const; + void set_emission_energy(real_t p_energy); + real_t get_emission_energy() const; - void set_albedo_mix(float p_mix); - float get_albedo_mix() const; + void set_albedo_mix(real_t p_mix); + real_t get_albedo_mix() const; void set_modulate(Color p_modulate); Color get_modulate() const; - void set_upper_fade(float p_energy); - float get_upper_fade() const; + void set_upper_fade(real_t p_energy); + real_t get_upper_fade() const; - void set_lower_fade(float p_fade); - float get_lower_fade() const; + void set_lower_fade(real_t p_fade); + real_t get_lower_fade() const; - void set_normal_fade(float p_fade); - float get_normal_fade() const; + void set_normal_fade(real_t p_fade); + real_t get_normal_fade() const; void set_enable_distance_fade(bool p_enable); bool is_distance_fade_enabled() const; - void set_distance_fade_begin(float p_distance); - float get_distance_fade_begin() const; + void set_distance_fade_begin(real_t p_distance); + real_t get_distance_fade_begin() const; - void set_distance_fade_length(float p_length); - float get_distance_fade_length() const; + void set_distance_fade_length(real_t p_length); + real_t get_distance_fade_length() const; void set_cull_mask(uint32_t p_layers); uint32_t get_cull_mask() const; diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp index f78027e6c7..baf28ae102 100644 --- a/scene/3d/gpu_particles_3d.cpp +++ b/scene/3d/gpu_particles_3d.cpp @@ -30,11 +30,8 @@ #include "gpu_particles_3d.h" -#include "core/os/os.h" #include "scene/resources/particles_material.h" -#include "servers/rendering_server.h" - AABB GPUParticles3D::get_aabb() const { return AABB(); } @@ -59,7 +56,7 @@ void GPUParticles3D::set_amount(int p_amount) { RS::get_singleton()->particles_set_amount(particles, amount); } -void GPUParticles3D::set_lifetime(float p_lifetime) { +void GPUParticles3D::set_lifetime(double p_lifetime) { ERR_FAIL_COND_MSG(p_lifetime <= 0, "Particles lifetime must be greater than 0."); lifetime = p_lifetime; RS::get_singleton()->particles_set_lifetime(particles, lifetime); @@ -81,17 +78,17 @@ void GPUParticles3D::set_one_shot(bool p_one_shot) { } } -void GPUParticles3D::set_pre_process_time(float p_time) { +void GPUParticles3D::set_pre_process_time(double p_time) { pre_process_time = p_time; RS::get_singleton()->particles_set_pre_process_time(particles, pre_process_time); } -void GPUParticles3D::set_explosiveness_ratio(float p_ratio) { +void GPUParticles3D::set_explosiveness_ratio(real_t p_ratio) { explosiveness_ratio = p_ratio; RS::get_singleton()->particles_set_explosiveness_ratio(particles, explosiveness_ratio); } -void GPUParticles3D::set_randomness_ratio(float p_ratio) { +void GPUParticles3D::set_randomness_ratio(real_t p_ratio) { randomness_ratio = p_ratio; RS::get_singleton()->particles_set_randomness_ratio(particles, randomness_ratio); } @@ -99,7 +96,7 @@ void GPUParticles3D::set_randomness_ratio(float p_ratio) { void GPUParticles3D::set_visibility_aabb(const AABB &p_aabb) { visibility_aabb = p_aabb; RS::get_singleton()->particles_set_custom_aabb(particles, visibility_aabb); - update_gizmo(); + update_gizmos(); } void GPUParticles3D::set_use_local_coordinates(bool p_enable) { @@ -118,12 +115,12 @@ void GPUParticles3D::set_process_material(const Ref<Material> &p_material) { update_configuration_warnings(); } -void GPUParticles3D::set_speed_scale(float p_scale) { +void GPUParticles3D::set_speed_scale(double p_scale) { speed_scale = p_scale; RS::get_singleton()->particles_set_speed_scale(particles, p_scale); } -void GPUParticles3D::set_collision_base_size(float p_size) { +void GPUParticles3D::set_collision_base_size(real_t p_size) { collision_base_size = p_size; RS::get_singleton()->particles_set_collision_base_size(particles, p_size); } @@ -136,7 +133,7 @@ int GPUParticles3D::get_amount() const { return amount; } -float GPUParticles3D::get_lifetime() const { +double GPUParticles3D::get_lifetime() const { return lifetime; } @@ -144,15 +141,15 @@ bool GPUParticles3D::get_one_shot() const { return one_shot; } -float GPUParticles3D::get_pre_process_time() const { +double GPUParticles3D::get_pre_process_time() const { return pre_process_time; } -float GPUParticles3D::get_explosiveness_ratio() const { +real_t GPUParticles3D::get_explosiveness_ratio() const { return explosiveness_ratio; } -float GPUParticles3D::get_randomness_ratio() const { +real_t GPUParticles3D::get_randomness_ratio() const { return randomness_ratio; } @@ -168,11 +165,11 @@ Ref<Material> GPUParticles3D::get_process_material() const { return process_material; } -float GPUParticles3D::get_speed_scale() const { +double GPUParticles3D::get_speed_scale() const { return speed_scale; } -float GPUParticles3D::get_collision_base_size() const { +real_t GPUParticles3D::get_collision_base_size() const { return collision_base_size; } @@ -186,7 +183,8 @@ void GPUParticles3D::set_trail_enabled(bool p_enabled) { RS::get_singleton()->particles_set_trails(particles, trail_enabled, trail_length); update_configuration_warnings(); } -void GPUParticles3D::set_trail_length(float p_seconds) { + +void GPUParticles3D::set_trail_length(double p_seconds) { ERR_FAIL_COND(p_seconds < 0.001); trail_length = p_seconds; RS::get_singleton()->particles_set_trails(particles, trail_enabled, trail_length); @@ -195,7 +193,8 @@ void GPUParticles3D::set_trail_length(float p_seconds) { bool GPUParticles3D::is_trail_enabled() const { return trail_enabled; } -float GPUParticles3D::get_trail_length() const { + +double GPUParticles3D::get_trail_length() const { return trail_length; } @@ -313,7 +312,7 @@ TypedArray<String> GPUParticles3D::get_configuration_warnings() const { } else { const ParticlesMaterial *process = Object::cast_to<ParticlesMaterial>(process_material.ptr()); if (!anim_material_found && process && - (process->get_param(ParticlesMaterial::PARAM_ANIM_SPEED) != 0.0 || process->get_param(ParticlesMaterial::PARAM_ANIM_OFFSET) != 0.0 || + (process->get_param_max(ParticlesMaterial::PARAM_ANIM_SPEED) != 0.0 || process->get_param_max(ParticlesMaterial::PARAM_ANIM_OFFSET) != 0.0 || process->get_param_texture(ParticlesMaterial::PARAM_ANIM_SPEED).is_valid() || process->get_param_texture(ParticlesMaterial::PARAM_ANIM_OFFSET).is_valid())) { warnings.push_back(TTR("Particles animation requires the usage of a BaseMaterial3D whose Billboard Mode is set to \"Particle Billboard\".")); } @@ -485,6 +484,7 @@ void GPUParticles3D::set_skin(const Ref<Skin> &p_skin) { skin = p_skin; _skinning_changed(); } + Ref<Skin> GPUParticles3D::get_skin() const { return skin; } diff --git a/scene/3d/gpu_particles_3d.h b/scene/3d/gpu_particles_3d.h index 7b21cf03f1..5e96f660da 100644 --- a/scene/3d/gpu_particles_3d.h +++ b/scene/3d/gpu_particles_3d.h @@ -31,9 +31,7 @@ #ifndef PARTICLES_H #define PARTICLES_H -#include "core/templates/rid.h" #include "scene/3d/visual_instance_3d.h" -#include "scene/resources/material.h" #include "scene/resources/skin.h" class GPUParticles3D : public GeometryInstance3D { @@ -64,21 +62,21 @@ private: bool one_shot; int amount; - float lifetime; - float pre_process_time; - float explosiveness_ratio; - float randomness_ratio; - float speed_scale; + double lifetime; + double pre_process_time; + real_t explosiveness_ratio; + real_t randomness_ratio; + double speed_scale; AABB visibility_aabb; bool local_coords; int fixed_fps; bool fractional_delta; bool interpolate = true; NodePath sub_emitter; - float collision_base_size = 0.01; + real_t collision_base_size = 0.01; bool trail_enabled = false; - float trail_length = 0.3; + double trail_length = 0.3; TransformAlign transform_align = TRANSFORM_ALIGN_DISABLED; @@ -104,33 +102,33 @@ public: void set_emitting(bool p_emitting); void set_amount(int p_amount); - void set_lifetime(float p_lifetime); + void set_lifetime(double p_lifetime); void set_one_shot(bool p_one_shot); - void set_pre_process_time(float p_time); - void set_explosiveness_ratio(float p_ratio); - void set_randomness_ratio(float p_ratio); + void set_pre_process_time(double p_time); + void set_explosiveness_ratio(real_t p_ratio); + void set_randomness_ratio(real_t p_ratio); void set_visibility_aabb(const AABB &p_aabb); void set_use_local_coordinates(bool p_enable); void set_process_material(const Ref<Material> &p_material); - void set_speed_scale(float p_scale); - void set_collision_base_size(float p_ratio); + void set_speed_scale(double p_scale); + void set_collision_base_size(real_t p_ratio); void set_trail_enabled(bool p_enabled); - void set_trail_length(float p_seconds); + void set_trail_length(double p_seconds); bool is_emitting() const; int get_amount() const; - float get_lifetime() const; + double get_lifetime() const; bool get_one_shot() const; - float get_pre_process_time() const; - float get_explosiveness_ratio() const; - float get_randomness_ratio() const; + double get_pre_process_time() const; + real_t get_explosiveness_ratio() const; + real_t get_randomness_ratio() const; AABB get_visibility_aabb() const; bool get_use_local_coordinates() const; Ref<Material> get_process_material() const; - float get_speed_scale() const; - float get_collision_base_size() const; + double get_speed_scale() const; + real_t get_collision_base_size() const; bool is_trail_enabled() const; - float get_trail_length() const; + double get_trail_length() const; void set_fixed_fps(int p_count); int get_fixed_fps() const; diff --git a/scene/3d/gpu_particles_collision_3d.cpp b/scene/3d/gpu_particles_collision_3d.cpp index c1e71b9565..a34a30913e 100644 --- a/scene/3d/gpu_particles_collision_3d.cpp +++ b/scene/3d/gpu_particles_collision_3d.cpp @@ -30,7 +30,6 @@ #include "gpu_particles_collision_3d.h" -#include "core/templates/thread_work_pool.h" #include "mesh_instance_3d.h" #include "scene/3d/camera_3d.h" #include "scene/main/viewport.h" @@ -70,13 +69,13 @@ void GPUParticlesCollisionSphere::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater"), "set_radius", "get_radius"); } -void GPUParticlesCollisionSphere::set_radius(float p_radius) { +void GPUParticlesCollisionSphere::set_radius(real_t p_radius) { radius = p_radius; RS::get_singleton()->particles_collision_set_sphere_radius(_get_collision(), radius); - update_gizmo(); + update_gizmos(); } -float GPUParticlesCollisionSphere::get_radius() const { +real_t GPUParticlesCollisionSphere::get_radius() const { return radius; } @@ -103,7 +102,7 @@ void GPUParticlesCollisionBox::_bind_methods() { void GPUParticlesCollisionBox::set_extents(const Vector3 &p_extents) { extents = p_extents; RS::get_singleton()->particles_collision_set_box_extents(_get_collision(), extents); - update_gizmo(); + update_gizmos(); } Vector3 GPUParticlesCollisionBox::get_extents() const { @@ -217,7 +216,7 @@ uint32_t GPUParticlesCollisionSDF::_create_bvh(LocalVector<BVH> &bvh_tree, FaceP return index; } -static _FORCE_INLINE_ float Vector3_dot2(const Vector3 &p_vec3) { +static _FORCE_INLINE_ real_t Vector3_dot2(const Vector3 &p_vec3) { return p_vec3.dot(p_vec3); } @@ -397,9 +396,7 @@ Ref<Image> GPUParticlesCollisionSDF::bake() { bake_step_function(0, "Finding Meshes"); } - for (List<PlotMesh>::Element *E = plot_meshes.front(); E; E = E->next()) { - const PlotMesh &pm = E->get(); - + for (const PlotMesh &pm : plot_meshes) { for (int i = 0; i < pm.mesh->get_surface_count(); i++) { if (pm.mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { continue; //only triangles @@ -545,7 +542,7 @@ float GPUParticlesCollisionSDF::get_thickness() const { void GPUParticlesCollisionSDF::set_extents(const Vector3 &p_extents) { extents = p_extents; RS::get_singleton()->particles_collision_set_box_extents(_get_collision(), extents); - update_gizmo(); + update_gizmos(); } Vector3 GPUParticlesCollisionSDF::get_extents() const { @@ -554,7 +551,7 @@ Vector3 GPUParticlesCollisionSDF::get_extents() const { void GPUParticlesCollisionSDF::set_resolution(Resolution p_resolution) { resolution = p_resolution; - update_gizmo(); + update_gizmos(); } GPUParticlesCollisionSDF::Resolution GPUParticlesCollisionSDF::get_resolution() const { @@ -596,7 +593,7 @@ void GPUParticlesCollisionHeightField::_notification(int p_what) { } if (follow_camera_mode && get_viewport()) { - Camera3D *cam = get_viewport()->get_camera(); + 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(); @@ -680,7 +677,7 @@ float GPUParticlesCollisionHeightField::get_follow_camera_push_ratio() const { void GPUParticlesCollisionHeightField::set_extents(const Vector3 &p_extents) { extents = p_extents; RS::get_singleton()->particles_collision_set_box_extents(_get_collision(), extents); - update_gizmo(); + update_gizmos(); RS::get_singleton()->particles_collision_height_field_update(_get_collision()); } @@ -691,7 +688,7 @@ Vector3 GPUParticlesCollisionHeightField::get_extents() const { void GPUParticlesCollisionHeightField::set_resolution(Resolution p_resolution) { resolution = p_resolution; RS::get_singleton()->particles_collision_set_height_field_resolution(_get_collision(), RS::ParticlesCollisionHeightfieldResolution(resolution)); - update_gizmo(); + update_gizmos(); RS::get_singleton()->particles_collision_height_field_update(_get_collision()); } @@ -740,31 +737,31 @@ uint32_t GPUParticlesAttractor3D::get_cull_mask() const { return cull_mask; } -void GPUParticlesAttractor3D::set_strength(float p_strength) { +void GPUParticlesAttractor3D::set_strength(real_t p_strength) { strength = p_strength; RS::get_singleton()->particles_collision_set_attractor_strength(collision, p_strength); } -float GPUParticlesAttractor3D::get_strength() const { +real_t GPUParticlesAttractor3D::get_strength() const { return strength; } -void GPUParticlesAttractor3D::set_attenuation(float p_attenuation) { +void GPUParticlesAttractor3D::set_attenuation(real_t p_attenuation) { attenuation = p_attenuation; RS::get_singleton()->particles_collision_set_attractor_attenuation(collision, p_attenuation); } -float GPUParticlesAttractor3D::get_attenuation() const { +real_t GPUParticlesAttractor3D::get_attenuation() const { return attenuation; } -void GPUParticlesAttractor3D::set_directionality(float p_directionality) { +void GPUParticlesAttractor3D::set_directionality(real_t p_directionality) { directionality = p_directionality; RS::get_singleton()->particles_collision_set_attractor_directionality(collision, p_directionality); - update_gizmo(); + update_gizmos(); } -float GPUParticlesAttractor3D::get_directionality() const { +real_t GPUParticlesAttractor3D::get_directionality() const { return directionality; } @@ -805,13 +802,13 @@ void GPUParticlesAttractorSphere::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater"), "set_radius", "get_radius"); } -void GPUParticlesAttractorSphere::set_radius(float p_radius) { +void GPUParticlesAttractorSphere::set_radius(real_t p_radius) { radius = p_radius; RS::get_singleton()->particles_collision_set_sphere_radius(_get_collision(), radius); - update_gizmo(); + update_gizmos(); } -float GPUParticlesAttractorSphere::get_radius() const { +real_t GPUParticlesAttractorSphere::get_radius() const { return radius; } @@ -838,7 +835,7 @@ void GPUParticlesAttractorBox::_bind_methods() { void GPUParticlesAttractorBox::set_extents(const Vector3 &p_extents) { extents = p_extents; RS::get_singleton()->particles_collision_set_box_extents(_get_collision(), extents); - update_gizmo(); + update_gizmos(); } Vector3 GPUParticlesAttractorBox::get_extents() const { @@ -872,7 +869,7 @@ void GPUParticlesAttractorVectorField::_bind_methods() { void GPUParticlesAttractorVectorField::set_extents(const Vector3 &p_extents) { extents = p_extents; RS::get_singleton()->particles_collision_set_box_extents(_get_collision(), extents); - update_gizmo(); + update_gizmos(); } Vector3 GPUParticlesAttractorVectorField::get_extents() const { diff --git a/scene/3d/gpu_particles_collision_3d.h b/scene/3d/gpu_particles_collision_3d.h index c55463378d..fbf68ed6df 100644 --- a/scene/3d/gpu_particles_collision_3d.h +++ b/scene/3d/gpu_particles_collision_3d.h @@ -32,9 +32,7 @@ #define GPU_PARTICLES_COLLISION_3D_H #include "core/templates/local_vector.h" -#include "core/templates/rid.h" #include "scene/3d/visual_instance_3d.h" -#include "scene/resources/material.h" class GPUParticlesCollision3D : public VisualInstance3D { GDCLASS(GPUParticlesCollision3D, VisualInstance3D); @@ -60,14 +58,14 @@ public: class GPUParticlesCollisionSphere : public GPUParticlesCollision3D { GDCLASS(GPUParticlesCollisionSphere, GPUParticlesCollision3D); - float radius = 1.0; + real_t radius = 1.0; protected: static void _bind_methods(); public: - void set_radius(float p_radius); - float get_radius() const; + void set_radius(real_t p_radius); + real_t get_radius() const; virtual AABB get_aabb() const override; @@ -253,9 +251,9 @@ class GPUParticlesAttractor3D : public VisualInstance3D { uint32_t cull_mask = 0xFFFFFFFF; RID collision; - float strength = 1.0; - float attenuation = 1.0; - float directionality = 0.0; + real_t strength = 1.0; + real_t attenuation = 1.0; + real_t directionality = 0.0; protected: _FORCE_INLINE_ RID _get_collision() { return collision; } @@ -267,14 +265,14 @@ public: void set_cull_mask(uint32_t p_cull_mask); uint32_t get_cull_mask() const; - void set_strength(float p_strength); - float get_strength() const; + void set_strength(real_t p_strength); + real_t get_strength() const; - void set_attenuation(float p_attenuation); - float get_attenuation() const; + void set_attenuation(real_t p_attenuation); + real_t get_attenuation() const; - void set_directionality(float p_directionality); - float get_directionality() const; + void set_directionality(real_t p_directionality); + real_t get_directionality() const; virtual Vector<Face3> get_faces(uint32_t p_usage_flags) const override { return Vector<Face3>(); } @@ -284,14 +282,14 @@ public: class GPUParticlesAttractorSphere : public GPUParticlesAttractor3D { GDCLASS(GPUParticlesAttractorSphere, GPUParticlesAttractor3D); - float radius = 1.0; + real_t radius = 1.0; protected: static void _bind_methods(); public: - void set_radius(float p_radius); - float get_radius() const; + void set_radius(real_t p_radius); + real_t get_radius() const; virtual AABB get_aabb() const override; diff --git a/scene/3d/light_3d.cpp b/scene/3d/light_3d.cpp index 8478821ba1..ab417fafdd 100644 --- a/scene/3d/light_3d.cpp +++ b/scene/3d/light_3d.cpp @@ -30,22 +30,18 @@ #include "light_3d.h" -#include "core/config/engine.h" -#include "core/config/project_settings.h" -#include "scene/resources/surface_tool.h" - bool Light3D::_can_gizmo_scale() const { return false; } -void Light3D::set_param(Param p_param, float p_value) { +void Light3D::set_param(Param p_param, real_t p_value) { ERR_FAIL_INDEX(p_param, PARAM_MAX); param[p_param] = p_value; RS::get_singleton()->light_set_param(light, RS::LightParam(p_param), p_value); if (p_param == PARAM_SPOT_ANGLE || p_param == PARAM_RANGE) { - update_gizmo(); + update_gizmos(); if (p_param == PARAM_SPOT_ANGLE) { update_configuration_warnings(); @@ -53,7 +49,7 @@ void Light3D::set_param(Param p_param, float p_value) { } } -float Light3D::get_param(Param p_param) const { +real_t Light3D::get_param(Param p_param) const { ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0); return param[p_param]; } @@ -95,7 +91,7 @@ void Light3D::set_color(const Color &p_color) { color = p_color; RS::get_singleton()->light_set_color(light, p_color); // The gizmo color depends on the light color, so update it. - update_gizmo(); + update_gizmos(); } Color Light3D::get_color() const { @@ -128,8 +124,8 @@ AABB Light3D::get_aabb() const { return AABB(Vector3(-1, -1, -1) * param[PARAM_RANGE], Vector3(2, 2, 2) * param[PARAM_RANGE]); } else if (type == RenderingServer::LIGHT_SPOT) { - float len = param[PARAM_RANGE]; - float size = Math::tan(Math::deg2rad(param[PARAM_SPOT_ANGLE])) * len; + real_t len = param[PARAM_RANGE]; + real_t size = Math::tan(Math::deg2rad(param[PARAM_SPOT_ANGLE])) * len; return AABB(Vector3(-size, -size, -len), Vector3(size * 2, size * 2, len)); } @@ -344,7 +340,7 @@ Light3D::Light3D(RenderingServer::LightType p_type) { set_param(PARAM_SHADOW_FADE_START, 0.8); set_param(PARAM_SHADOW_PANCAKE_SIZE, 20.0); set_param(PARAM_SHADOW_BLUR, 1.0); - set_param(PARAM_SHADOW_BIAS, 0.02); + set_param(PARAM_SHADOW_BIAS, 0.03); set_param(PARAM_SHADOW_NORMAL_BIAS, 1.0); set_param(PARAM_TRANSMITTANCE_BIAS, 0.05); set_param(PARAM_SHADOW_VOLUMETRIC_FOG_FADE, 0.1); @@ -425,8 +421,7 @@ DirectionalLight3D::DirectionalLight3D() : set_param(PARAM_SHADOW_MAX_DISTANCE, 100); set_param(PARAM_SHADOW_FADE_START, 0.8); // Increase the default shadow bias to better suit most scenes. - // Leave normal bias untouched as it doesn't benefit DirectionalLight3D as much as OmniLight3D. - set_param(PARAM_SHADOW_BIAS, 0.05); + set_param(PARAM_SHADOW_BIAS, 0.1); set_shadow_mode(SHADOW_PARALLEL_4_SPLITS); blend_splits = false; } @@ -467,8 +462,7 @@ OmniLight3D::OmniLight3D() : Light3D(RenderingServer::LIGHT_OMNI) { set_shadow_mode(SHADOW_CUBE); // Increase the default shadow biases to better suit most scenes. - set_param(PARAM_SHADOW_BIAS, 0.1); - set_param(PARAM_SHADOW_NORMAL_BIAS, 2.0); + set_param(PARAM_SHADOW_BIAS, 0.2); } TypedArray<String> SpotLight3D::get_configuration_warnings() const { diff --git a/scene/3d/light_3d.h b/scene/3d/light_3d.h index d0308a3025..ecea60339f 100644 --- a/scene/3d/light_3d.h +++ b/scene/3d/light_3d.h @@ -32,8 +32,6 @@ #define LIGHT_3D_H #include "scene/3d/visual_instance_3d.h" -#include "scene/resources/texture.h" -#include "servers/rendering_server.h" class Light3D : public VisualInstance3D { GDCLASS(Light3D, VisualInstance3D); @@ -71,7 +69,7 @@ public: private: Color color; - float param[PARAM_MAX] = {}; + real_t param[PARAM_MAX] = {}; Color shadow_color; bool shadow = false; bool negative = false; @@ -102,8 +100,8 @@ public: void set_editor_only(bool p_editor_only); bool is_editor_only() const; - void set_param(Param p_param, float p_value); - float get_param(Param p_param) const; + void set_param(Param p_param, real_t p_value); + real_t get_param(Param p_param) const; void set_shadow(bool p_enable); bool has_shadow() const; diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp index 66e3535fc4..7dd083e314 100644 --- a/scene/3d/lightmap_gi.cpp +++ b/scene/3d/lightmap_gi.cpp @@ -31,14 +31,9 @@ #include "lightmap_gi.h" #include "core/io/config_file.h" -#include "core/io/dir_access.h" -#include "core/io/file_access.h" -#include "core/io/resource_saver.h" -#include "core/math/camera_matrix.h" #include "core/math/delaunay_3d.h" -#include "core/os/os.h" -#include "core/templates/sort_array.h" #include "lightmap_probe.h" +#include "scene/3d/mesh_instance_3d.h" void LightmapGIData::add_user(const NodePath &p_path, const Rect2 &p_uv_scale, int p_slice_index, int32_t p_sub_instance) { User user; @@ -599,7 +594,7 @@ void LightmapGI::_gen_new_positions_from_octree(const GenProbesOctree *p_cell, f const Vector3 *pp = probe_positions.ptr(); bool exists = false; for (int j = 0; j < ppcount; j++) { - if (pp[j].distance_to(real_pos) < CMP_EPSILON) { + if (pp[j].is_equal_approx(real_pos)) { exists = true; break; } @@ -1250,7 +1245,7 @@ void LightmapGI::set_light_data(const Ref<LightmapGIData> &p_data) { } } - update_gizmo(); + update_gizmos(); } Ref<LightmapGIData> LightmapGI::get_light_data() const { diff --git a/scene/3d/lightmap_gi.h b/scene/3d/lightmap_gi.h index 8a54512383..e73350fd64 100644 --- a/scene/3d/lightmap_gi.h +++ b/scene/3d/lightmap_gi.h @@ -34,10 +34,7 @@ #include "core/templates/local_vector.h" #include "scene/3d/light_3d.h" #include "scene/3d/lightmapper.h" -#include "scene/3d/mesh_instance_3d.h" -#include "scene/3d/multimesh_instance_3d.h" #include "scene/3d/visual_instance_3d.h" -#include "scene/resources/sky.h" class LightmapGIData : public Resource { GDCLASS(LightmapGIData, Resource); diff --git a/scene/3d/lightmapper.h b/scene/3d/lightmapper.h index 3a6a88d435..d028628901 100644 --- a/scene/3d/lightmapper.h +++ b/scene/3d/lightmapper.h @@ -31,8 +31,9 @@ #ifndef LIGHTMAPPER_H #define LIGHTMAPPER_H -#include "scene/resources/mesh.h" -#include "servers/rendering/rendering_device.h" +#include "core/object/ref_counted.h" + +class Image; #if !defined(__aligned) diff --git a/scene/3d/listener_3d.cpp b/scene/3d/listener_3d.cpp index 636be083ab..1c52933ee5 100644 --- a/scene/3d/listener_3d.cpp +++ b/scene/3d/listener_3d.cpp @@ -30,7 +30,7 @@ #include "listener_3d.h" -#include "scene/resources/mesh.h" +#include "scene/main/viewport.h" void Listener3D::_update_audio_listener_state() { } @@ -73,14 +73,14 @@ void Listener3D::_get_property_list(List<PropertyInfo> *p_list) const { void Listener3D::_update_listener() { if (is_inside_tree() && is_current()) { - get_viewport()->_listener_transform_changed_notify(); + get_viewport()->_listener_transform_3d_changed_notify(); } } void Listener3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_WORLD: { - bool first_listener = get_viewport()->_listener_add(this); + bool first_listener = get_viewport()->_listener_3d_add(this); if (!get_tree()->is_node_being_edited(this) && (current || first_listener)) { make_current(); } @@ -99,7 +99,7 @@ void Listener3D::_notification(int p_what) { } } - get_viewport()->_listener_remove(this); + get_viewport()->_listener_3d_remove(this); } break; } @@ -116,7 +116,7 @@ void Listener3D::make_current() { return; } - get_viewport()->_listener_set(this); + get_viewport()->_listener_3d_set(this); } void Listener3D::clear_current() { @@ -125,15 +125,15 @@ void Listener3D::clear_current() { return; } - if (get_viewport()->get_listener() == this) { - get_viewport()->_listener_set(nullptr); - get_viewport()->_listener_make_next_current(this); + if (get_viewport()->get_listener_3d() == this) { + get_viewport()->_listener_3d_set(nullptr); + get_viewport()->_listener_3d_make_next_current(this); } } bool Listener3D::is_current() const { if (is_inside_tree() && !get_tree()->is_node_being_edited(this)) { - return get_viewport()->get_listener() == this; + return get_viewport()->get_listener_3d() == this; } else { return current; } diff --git a/scene/3d/listener_3d.h b/scene/3d/listener_3d.h index bcc66f167c..25eacf5135 100644 --- a/scene/3d/listener_3d.h +++ b/scene/3d/listener_3d.h @@ -32,7 +32,6 @@ #define LISTENER_3D_H #include "scene/3d/node_3d.h" -#include "scene/main/window.h" class Listener3D : public Node3D { GDCLASS(Listener3D, Node3D); diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp index 08dec232ab..7e7db57af3 100644 --- a/scene/3d/mesh_instance_3d.cpp +++ b/scene/3d/mesh_instance_3d.cpp @@ -33,8 +33,6 @@ #include "collision_shape_3d.h" #include "core/core_string_names.h" #include "physics_body_3d.h" -#include "scene/resources/material.h" -#include "skeleton_3d.h" bool MeshInstance3D::_set(const StringName &p_name, const Variant &p_value) { //this is not _too_ bad performance wise, really. it only arrives here if the property was not set anywhere else. @@ -94,13 +92,13 @@ void MeshInstance3D::_get_property_list(List<PropertyInfo> *p_list) const { ls.sort(); - for (List<String>::Element *E = ls.front(); E; E = E->next()) { - p_list->push_back(PropertyInfo(Variant::FLOAT, E->get(), PROPERTY_HINT_RANGE, "-1,1,0.00001")); + for (const String &E : ls) { + p_list->push_back(PropertyInfo(Variant::FLOAT, E, PROPERTY_HINT_RANGE, "-1,1,0.00001")); } 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, "ShaderMaterial,StandardMaterial3D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DEFERRED_SET_RESOURCE)); + 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)); } } } @@ -133,7 +131,7 @@ void MeshInstance3D::set_mesh(const Ref<Mesh> &p_mesh) { set_base(RID()); } - update_gizmo(); + update_gizmos(); notify_property_list_changed(); } @@ -241,12 +239,12 @@ void MeshInstance3D::create_trimesh_collision() { } } -Node *MeshInstance3D::create_convex_collision_node() { +Node *MeshInstance3D::create_convex_collision_node(bool p_clean, bool p_simplify) { if (mesh.is_null()) { return nullptr; } - Ref<Shape3D> shape = mesh->create_convex_shape(); + Ref<Shape3D> shape = mesh->create_convex_shape(p_clean, p_simplify); if (shape.is_null()) { return nullptr; } @@ -258,8 +256,8 @@ Node *MeshInstance3D::create_convex_collision_node() { return static_body; } -void MeshInstance3D::create_convex_collision() { - StaticBody3D *static_body = Object::cast_to<StaticBody3D>(create_convex_collision_node()); +void MeshInstance3D::create_convex_collision(bool p_clean, bool p_simplify) { + StaticBody3D *static_body = Object::cast_to<StaticBody3D>(create_convex_collision_node(p_clean, p_simplify)); ERR_FAIL_COND(!static_body); static_body->set_name(String(get_name()) + "_col"); @@ -276,7 +274,8 @@ Node *MeshInstance3D::create_multiple_convex_collisions_node() { return nullptr; } - Vector<Ref<Shape3D>> shapes = mesh->convex_decompose(); + Mesh::ConvexDecompositionSettings settings; + Vector<Ref<Shape3D>> shapes = mesh->convex_decompose(settings); if (!shapes.size()) { return nullptr; } @@ -356,7 +355,7 @@ Ref<Material> MeshInstance3D::get_active_material(int p_surface) const { void MeshInstance3D::_mesh_changed() { ERR_FAIL_COND(mesh.is_null()); surface_override_materials.resize(mesh->get_surface_count()); - update_gizmo(); + update_gizmos(); } void MeshInstance3D::create_debug_tangents() { @@ -451,7 +450,7 @@ void MeshInstance3D::_bind_methods() { ClassDB::bind_method(D_METHOD("create_trimesh_collision"), &MeshInstance3D::create_trimesh_collision); ClassDB::set_method_flags("MeshInstance3D", "create_trimesh_collision", METHOD_FLAGS_DEFAULT); - ClassDB::bind_method(D_METHOD("create_convex_collision"), &MeshInstance3D::create_convex_collision); + ClassDB::bind_method(D_METHOD("create_convex_collision", "clean", "simplify"), &MeshInstance3D::create_convex_collision, DEFVAL(true), DEFVAL(false)); ClassDB::set_method_flags("MeshInstance3D", "create_convex_collision", METHOD_FLAGS_DEFAULT); ClassDB::bind_method(D_METHOD("create_multiple_convex_collisions"), &MeshInstance3D::create_multiple_convex_collisions); ClassDB::set_method_flags("MeshInstance3D", "create_multiple_convex_collisions", METHOD_FLAGS_DEFAULT); diff --git a/scene/3d/mesh_instance_3d.h b/scene/3d/mesh_instance_3d.h index 9dea5804e0..beb7f6cf95 100644 --- a/scene/3d/mesh_instance_3d.h +++ b/scene/3d/mesh_instance_3d.h @@ -31,10 +31,10 @@ #ifndef MESH_INSTANCE_H #define MESH_INSTANCE_H -#include "scene/3d/skeleton_3d.h" #include "scene/3d/visual_instance_3d.h" -#include "scene/resources/mesh.h" -#include "scene/resources/skin.h" + +class Skin; +class SkinReference; class MeshInstance3D : public GeometryInstance3D { GDCLASS(MeshInstance3D, GeometryInstance3D); @@ -83,8 +83,8 @@ public: Node *create_trimesh_collision_node(); void create_trimesh_collision(); - Node *create_convex_collision_node(); - void create_convex_collision(); + Node *create_convex_collision_node(bool p_clean = true, bool p_simplify = false); + void create_convex_collision(bool p_clean = true, bool p_simplify = false); Node *create_multiple_convex_collisions_node(); void create_multiple_convex_collisions(); diff --git a/scene/3d/navigation_agent_3d.cpp b/scene/3d/navigation_agent_3d.cpp index 64cfe4dca7..c2d5c757db 100644 --- a/scene/3d/navigation_agent_3d.cpp +++ b/scene/3d/navigation_agent_3d.cpp @@ -30,7 +30,6 @@ #include "navigation_agent_3d.h" -#include "core/config/engine.h" #include "servers/navigation_server_3d.h" void NavigationAgent3D::_bind_methods() { @@ -242,7 +241,7 @@ void NavigationAgent3D::_avoidance_done(Vector3 p_new_velocity) { } velocity_submitted = false; - emit_signal("velocity_computed", p_new_velocity); + emit_signal(SNAME("velocity_computed"), p_new_velocity); } TypedArray<String> NavigationAgent3D::get_configuration_warnings() const { @@ -296,7 +295,7 @@ void NavigationAgent3D::update_navigation() { navigation_path = NavigationServer3D::get_singleton()->map_get_path(agent_parent->get_world_3d()->get_navigation_map(), o, target_location, true); navigation_finished = false; nav_path_index = 0; - emit_signal("path_changed"); + emit_signal(SNAME("path_changed")); } if (navigation_path.size() == 0) { @@ -312,7 +311,7 @@ void NavigationAgent3D::update_navigation() { _check_distance_to_target(); nav_path_index -= 1; navigation_finished = true; - emit_signal("navigation_finished"); + emit_signal(SNAME("navigation_finished")); break; } } @@ -322,7 +321,7 @@ void NavigationAgent3D::update_navigation() { void NavigationAgent3D::_check_distance_to_target() { if (!target_reached) { if (distance_to_target() < target_desired_distance) { - emit_signal("target_reached"); + emit_signal(SNAME("target_reached")); target_reached = true; } } diff --git a/scene/3d/navigation_agent_3d.h b/scene/3d/navigation_agent_3d.h index 56da2d1acf..bebfdc5f7e 100644 --- a/scene/3d/navigation_agent_3d.h +++ b/scene/3d/navigation_agent_3d.h @@ -31,7 +31,6 @@ #ifndef NAVIGATION_AGENT_H #define NAVIGATION_AGENT_H -#include "core/templates/vector.h" #include "scene/main/node.h" class Node3D; diff --git a/scene/3d/navigation_obstacle_3d.h b/scene/3d/navigation_obstacle_3d.h index 2f78f624a4..ab0b158303 100644 --- a/scene/3d/navigation_obstacle_3d.h +++ b/scene/3d/navigation_obstacle_3d.h @@ -32,7 +32,6 @@ #define NAVIGATION_OBSTACLE_H #include "scene/3d/node_3d.h" -#include "scene/main/node.h" class NavigationObstacle3D : public Node { GDCLASS(NavigationObstacle3D, Node); diff --git a/scene/3d/navigation_region_3d.cpp b/scene/3d/navigation_region_3d.cpp index 0afad62404..8a51a259f7 100644 --- a/scene/3d/navigation_region_3d.cpp +++ b/scene/3d/navigation_region_3d.cpp @@ -30,7 +30,6 @@ #include "navigation_region_3d.h" -#include "core/os/thread.h" #include "mesh_instance_3d.h" #include "servers/navigation_server_3d.h" @@ -59,7 +58,7 @@ void NavigationRegion3D::set_enabled(bool p_enabled) { } } - update_gizmo(); + update_gizmos(); } bool NavigationRegion3D::is_enabled() const { @@ -132,9 +131,9 @@ void NavigationRegion3D::set_navigation_mesh(const Ref<NavigationMesh> &p_navmes Object::cast_to<MeshInstance3D>(debug_view)->set_mesh(navmesh->get_debug_mesh()); } - emit_signal("navigation_mesh_changed"); + emit_signal(SNAME("navigation_mesh_changed")); - update_gizmo(); + update_gizmos(); update_configuration_warnings(); } @@ -153,11 +152,11 @@ void _bake_navigation_mesh(void *p_user_data) { Ref<NavigationMesh> nav_mesh = args->nav_region->get_navigation_mesh()->duplicate(); NavigationServer3D::get_singleton()->region_bake_navmesh(nav_mesh, args->nav_region); - args->nav_region->call_deferred("_bake_finished", nav_mesh); + args->nav_region->call_deferred(SNAME("_bake_finished"), nav_mesh); memdelete(args); } else { ERR_PRINT("Can't bake the navigation mesh if the `NavigationMesh` resource doesn't exist"); - args->nav_region->call_deferred("_bake_finished", Ref<NavigationMesh>()); + args->nav_region->call_deferred(SNAME("_bake_finished"), Ref<NavigationMesh>()); memdelete(args); } } @@ -174,7 +173,7 @@ void NavigationRegion3D::bake_navigation_mesh() { void NavigationRegion3D::_bake_finished(Ref<NavigationMesh> p_nav_mesh) { set_navigation_mesh(p_nav_mesh); bake_thread.wait_to_finish(); - emit_signal("bake_finished"); + emit_signal(SNAME("bake_finished")); } TypedArray<String> NavigationRegion3D::get_configuration_warnings() const { @@ -211,7 +210,7 @@ void NavigationRegion3D::_bind_methods() { } void NavigationRegion3D::_navigation_changed() { - update_gizmo(); + update_gizmos(); update_configuration_warnings(); } diff --git a/scene/3d/navigation_region_3d.h b/scene/3d/navigation_region_3d.h index c2045215b1..ec7761ef93 100644 --- a/scene/3d/navigation_region_3d.h +++ b/scene/3d/navigation_region_3d.h @@ -32,7 +32,6 @@ #define NAVIGATION_REGION_H #include "scene/3d/node_3d.h" -#include "scene/resources/mesh.h" #include "scene/resources/navigation_mesh.h" class NavigationRegion3D : public Node3D { diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp index d6c6ec75b4..12470939f5 100644 --- a/scene/3d/node_3d.cpp +++ b/scene/3d/node_3d.cpp @@ -30,11 +30,9 @@ #include "node_3d.h" -#include "core/config/engine.h" #include "core/object/message_queue.h" #include "scene/3d/visual_instance_3d.h" -#include "scene/main/scene_tree.h" -#include "scene/main/window.h" +#include "scene/main/viewport.h" #include "scene/scene_string_names.h" /* @@ -76,7 +74,7 @@ Node3DGizmo::Node3DGizmo() { void Node3D::_notify_dirty() { #ifdef TOOLS_ENABLED - if ((data.gizmo.is_valid() || data.notify_transform) && !data.ignore_notification && !xform_change.in_list()) { + if ((!data.gizmos.is_empty() || data.notify_transform) && !data.ignore_notification && !xform_change.in_list()) { #else if (data.notify_transform && !data.ignore_notification && !xform_change.in_list()) { @@ -103,14 +101,14 @@ void Node3D::_propagate_transform_changed(Node3D *p_origin) { data.children_lock++; - for (List<Node3D *>::Element *E = data.children.front(); E; E = E->next()) { - if (E->get()->data.top_level_active) { + for (Node3D *&E : data.children) { + if (E->data.top_level_active) { continue; //don't propagate to a top_level } - E->get()->_propagate_transform_changed(p_origin); + E->_propagate_transform_changed(p_origin); } #ifdef TOOLS_ENABLED - if ((data.gizmo.is_valid() || data.notify_transform) && !data.ignore_notification && !xform_change.in_list()) { + if ((!data.gizmos.is_empty() || data.notify_transform) && !data.ignore_notification && !xform_change.in_list()) { #else if (data.notify_transform && !data.ignore_notification && !xform_change.in_list()) { #endif @@ -181,15 +179,14 @@ void Node3D::_notification(int p_what) { } #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint() && get_tree()->is_node_being_edited(this)) { - //get_scene()->call_group(SceneMainLoop::GROUP_CALL_REALTIME,SceneStringNames::get_singleton()->_spatial_editor_group,SceneStringNames::get_singleton()->_request_gizmo,this); get_tree()->call_group_flags(0, SceneStringNames::get_singleton()->_spatial_editor_group, SceneStringNames::get_singleton()->_request_gizmo, this); - if (!data.gizmo_disabled) { - if (data.gizmo.is_valid()) { - data.gizmo->create(); + if (!data.gizmos_disabled) { + for (int i = 0; i < data.gizmos.size(); i++) { + data.gizmos.write[i]->create(); if (is_visible_in_tree()) { - data.gizmo->redraw(); + data.gizmos.write[i]->redraw(); } - data.gizmo->transform(); + data.gizmos.write[i]->transform(); } } } @@ -198,10 +195,7 @@ void Node3D::_notification(int p_what) { } break; case NOTIFICATION_EXIT_WORLD: { #ifdef TOOLS_ENABLED - if (data.gizmo.is_valid()) { - data.gizmo->free(); - data.gizmo.unref(); - } + clear_gizmos(); #endif if (get_script_instance()) { @@ -215,8 +209,8 @@ void Node3D::_notification(int p_what) { case NOTIFICATION_TRANSFORM_CHANGED: { #ifdef TOOLS_ENABLED - if (data.gizmo.is_valid()) { - data.gizmo->transform(); + for (int i = 0; i < data.gizmos.size(); i++) { + data.gizmos.write[i]->transform(); } #endif } break; @@ -368,80 +362,119 @@ Vector3 Node3D::get_scale() const { return data.scale; } -void Node3D::update_gizmo() { +void Node3D::update_gizmos() { #ifdef TOOLS_ENABLED if (!is_inside_world()) { return; } - if (!data.gizmo.is_valid()) { - get_tree()->call_group_flags(SceneTree::GROUP_CALL_REALTIME, SceneStringNames::get_singleton()->_spatial_editor_group, SceneStringNames::get_singleton()->_request_gizmo, this); + + if (data.gizmos.is_empty()) { + return; } - if (!data.gizmo.is_valid()) { + data.gizmos_dirty = true; + MessageQueue::get_singleton()->push_callable(callable_mp(this, &Node3D::_update_gizmos)); +#endif +} + +void Node3D::clear_subgizmo_selection() { +#ifdef TOOLS_ENABLED + if (!is_inside_world()) { return; } - if (data.gizmo_dirty) { + + if (data.gizmos.is_empty()) { return; } - data.gizmo_dirty = true; - MessageQueue::get_singleton()->push_call(this, "_update_gizmo"); + + 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); + } #endif } -void Node3D::set_gizmo(const Ref<Node3DGizmo> &p_gizmo) { +void Node3D::add_gizmo(Ref<Node3DGizmo> p_gizmo) { #ifdef TOOLS_ENABLED - if (data.gizmo_disabled) { + if (data.gizmos_disabled || p_gizmo.is_null()) { return; } - if (data.gizmo.is_valid() && is_inside_world()) { - data.gizmo->free(); - } - data.gizmo = p_gizmo; - if (data.gizmo.is_valid() && is_inside_world()) { - data.gizmo->create(); + data.gizmos.push_back(p_gizmo); + + if (p_gizmo.is_valid() && is_inside_world()) { + p_gizmo->create(); if (is_visible_in_tree()) { - data.gizmo->redraw(); + p_gizmo->redraw(); } - data.gizmo->transform(); + p_gizmo->transform(); + } +#endif +} + +void Node3D::remove_gizmo(Ref<Node3DGizmo> p_gizmo) { +#ifdef TOOLS_ENABLED + + int idx = data.gizmos.find(p_gizmo); + if (idx != -1) { + p_gizmo->free(); + data.gizmos.remove(idx); } +#endif +} +void Node3D::clear_gizmos() { +#ifdef TOOLS_ENABLED + for (int i = 0; i < data.gizmos.size(); i++) { + data.gizmos.write[i]->free(); + } + data.gizmos.clear(); #endif } -Ref<Node3DGizmo> Node3D::get_gizmo() const { +Array Node3D::get_gizmos_bind() const { + Array ret; + #ifdef TOOLS_ENABLED + for (int i = 0; i < data.gizmos.size(); i++) { + ret.push_back(Variant(data.gizmos[i].ptr())); + } +#endif - return data.gizmo; + return ret; +} + +Vector<Ref<Node3DGizmo>> Node3D::get_gizmos() const { +#ifdef TOOLS_ENABLED + + return data.gizmos; #else - return Ref<Node3DGizmo>(); + return Vector<Ref<Node3DGizmo>>(); #endif } -void Node3D::_update_gizmo() { +void Node3D::_update_gizmos() { #ifdef TOOLS_ENABLED - if (!is_inside_world()) { + if (data.gizmos_disabled || !is_inside_world() || !data.gizmos_dirty) { return; } - data.gizmo_dirty = false; - if (data.gizmo.is_valid()) { + data.gizmos_dirty = false; + for (int i = 0; i < data.gizmos.size(); i++) { if (is_visible_in_tree()) { - data.gizmo->redraw(); + data.gizmos.write[i]->redraw(); } else { - data.gizmo->clear(); + data.gizmos.write[i]->clear(); } } #endif } #ifdef TOOLS_ENABLED -void Node3D::set_disable_gizmo(bool p_enabled) { - data.gizmo_disabled = p_enabled; - if (!p_enabled && data.gizmo.is_valid()) { - data.gizmo = Ref<Node3DGizmo>(); +void Node3D::set_disable_gizmos(bool p_enabled) { + data.gizmos_disabled = p_enabled; + if (!p_enabled) { + clear_gizmos(); } } - #endif void Node3D::set_disable_scale(bool p_enabled) { @@ -486,13 +519,13 @@ void Node3D::_propagate_visibility_changed() { notification(NOTIFICATION_VISIBILITY_CHANGED); emit_signal(SceneStringNames::get_singleton()->visibility_changed); #ifdef TOOLS_ENABLED - if (data.gizmo.is_valid()) { - _update_gizmo(); + if (!data.gizmos.is_empty()) { + data.gizmos_dirty = true; + _update_gizmos(); } #endif - for (List<Node3D *>::Element *E = data.children.front(); E; E = E->next()) { - Node3D *c = E->get(); + for (Node3D *c : data.children) { if (!c || !c->data.visible) { continue; } @@ -553,31 +586,31 @@ bool Node3D::is_visible() const { return data.visible; } -void Node3D::rotate_object_local(const Vector3 &p_axis, float p_angle) { +void Node3D::rotate_object_local(const Vector3 &p_axis, real_t p_angle) { Transform3D t = get_transform(); t.basis.rotate_local(p_axis, p_angle); set_transform(t); } -void Node3D::rotate(const Vector3 &p_axis, float p_angle) { +void Node3D::rotate(const Vector3 &p_axis, real_t p_angle) { Transform3D t = get_transform(); t.basis.rotate(p_axis, p_angle); set_transform(t); } -void Node3D::rotate_x(float p_angle) { +void Node3D::rotate_x(real_t p_angle) { Transform3D t = get_transform(); t.basis.rotate(Vector3(1, 0, 0), p_angle); set_transform(t); } -void Node3D::rotate_y(float p_angle) { +void Node3D::rotate_y(real_t p_angle) { Transform3D t = get_transform(); t.basis.rotate(Vector3(0, 1, 0), p_angle); set_transform(t); } -void Node3D::rotate_z(float p_angle) { +void Node3D::rotate_z(real_t p_angle) { Transform3D t = get_transform(); t.basis.rotate(Vector3(0, 0, 1), p_angle); set_transform(t); @@ -609,7 +642,7 @@ void Node3D::scale_object_local(const Vector3 &p_scale) { set_transform(t); } -void Node3D::global_rotate(const Vector3 &p_axis, float p_angle) { +void Node3D::global_rotate(const Vector3 &p_axis, real_t p_angle) { Transform3D t = get_global_transform(); t.basis.rotate(p_axis, p_angle); set_global_transform(t); @@ -638,19 +671,17 @@ void Node3D::set_identity() { } void Node3D::look_at(const Vector3 &p_target, const Vector3 &p_up) { - Vector3 origin(get_global_transform().origin); + Vector3 origin = get_global_transform().origin; look_at_from_position(origin, p_target, p_up); } void Node3D::look_at_from_position(const Vector3 &p_pos, const Vector3 &p_target, const Vector3 &p_up) { - ERR_FAIL_COND_MSG(p_pos == p_target, "Node origin and target are in the same position, look_at() failed."); - ERR_FAIL_COND_MSG(p_up.cross(p_target - p_pos) == Vector3(), "Up vector and direction between node origin and target are aligned, look_at() failed."); + ERR_FAIL_COND_MSG(p_pos.is_equal_approx(p_target), "Node origin and target are in the same position, look_at() failed."); + ERR_FAIL_COND_MSG(p_up.is_equal_approx(Vector3()), "The up vector can't be zero, look_at() failed."); + ERR_FAIL_COND_MSG(p_up.cross(p_target - p_pos).is_equal_approx(Vector3()), "Up vector and direction between node origin and target are aligned, look_at() failed."); - Transform3D lookat; - lookat.origin = p_pos; - - Vector3 original_scale(get_scale()); - lookat = lookat.looking_at(p_target, p_up); + Transform3D lookat = Transform3D(Basis::looking_at(p_target - p_pos, p_up), p_pos); + Vector3 original_scale = get_scale(); set_global_transform(lookat); set_scale(original_scale); } @@ -717,8 +748,7 @@ void Node3D::_update_visibility_parent(bool p_update_root) { RS::get_singleton()->instance_set_visibility_parent(vi->get_instance(), data.visibility_parent); } - for (List<Node3D *>::Element *E = data.children.front(); E; E = E->next()) { - Node3D *c = E->get(); + for (Node3D *c : data.children) { c->_update_visibility_parent(false); } } @@ -758,11 +788,11 @@ void Node3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_visibility_parent", "path"), &Node3D::set_visibility_parent); ClassDB::bind_method(D_METHOD("get_visibility_parent"), &Node3D::get_visibility_parent); - ClassDB::bind_method(D_METHOD("_update_gizmo"), &Node3D::_update_gizmo); - - ClassDB::bind_method(D_METHOD("update_gizmo"), &Node3D::update_gizmo); - ClassDB::bind_method(D_METHOD("set_gizmo", "gizmo"), &Node3D::set_gizmo); - ClassDB::bind_method(D_METHOD("get_gizmo"), &Node3D::get_gizmo); + ClassDB::bind_method(D_METHOD("update_gizmos"), &Node3D::update_gizmos); + ClassDB::bind_method(D_METHOD("add_gizmo", "gizmo"), &Node3D::add_gizmo); + ClassDB::bind_method(D_METHOD("get_gizmos"), &Node3D::get_gizmos_bind); + ClassDB::bind_method(D_METHOD("clear_gizmos"), &Node3D::clear_gizmos); + ClassDB::bind_method(D_METHOD("clear_subgizmo_selection"), &Node3D::clear_subgizmo_selection); ClassDB::bind_method(D_METHOD("set_visible", "visible"), &Node3D::set_visible); ClassDB::bind_method(D_METHOD("is_visible"), &Node3D::is_visible); @@ -813,7 +843,6 @@ void Node3D::_bind_methods() { 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"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gizmo", PROPERTY_HINT_RESOURCE_TYPE, "Node3DGizmo", PROPERTY_USAGE_NONE), "set_gizmo", "get_gizmo"); ADD_SIGNAL(MethodInfo("visibility_changed")); } diff --git a/scene/3d/node_3d.h b/scene/3d/node_3d.h index fe6324c796..0fd0c4e205 100644 --- a/scene/3d/node_3d.h +++ b/scene/3d/node_3d.h @@ -32,7 +32,6 @@ #define NODE_3D_H #include "scene/main/node.h" -#include "scene/main/scene_tree.h" class Node3DGizmo : public RefCounted { GDCLASS(Node3DGizmo, RefCounted); @@ -90,16 +89,16 @@ class Node3D : public Node { bool disable_scale = false; #ifdef TOOLS_ENABLED - Ref<Node3DGizmo> gizmo; - bool gizmo_disabled = false; - bool gizmo_dirty = false; + Vector<Ref<Node3DGizmo>> gizmos; + bool gizmos_disabled = false; + bool gizmos_dirty = false; #endif } data; NodePath visibility_parent_path; - void _update_gizmo(); + void _update_gizmos(); void _notify_dirty(); void _propagate_transform_changed(Node3D *p_origin); @@ -154,27 +153,31 @@ public: void set_disable_scale(bool p_enabled); bool is_scale_disabled() const; - void set_disable_gizmo(bool p_enabled); - void update_gizmo(); - void set_gizmo(const Ref<Node3DGizmo> &p_gizmo); - Ref<Node3DGizmo> get_gizmo() const; + void set_disable_gizmos(bool p_enabled); + void update_gizmos(); + void clear_subgizmo_selection(); + Vector<Ref<Node3DGizmo>> get_gizmos() const; + Array get_gizmos_bind() const; + void add_gizmo(Ref<Node3DGizmo> p_gizmo); + void remove_gizmo(Ref<Node3DGizmo> p_gizmo); + void clear_gizmos(); _FORCE_INLINE_ bool is_inside_world() const { return data.inside_world; } Transform3D get_relative_transform(const Node *p_parent) const; - void rotate(const Vector3 &p_axis, float p_angle); - void rotate_x(float p_angle); - void rotate_y(float p_angle); - void rotate_z(float p_angle); + void rotate(const Vector3 &p_axis, real_t p_angle); + void rotate_x(real_t p_angle); + void rotate_y(real_t p_angle); + void rotate_z(real_t p_angle); void translate(const Vector3 &p_offset); void scale(const Vector3 &p_ratio); - void rotate_object_local(const Vector3 &p_axis, float p_angle); + void rotate_object_local(const Vector3 &p_axis, real_t p_angle); void scale_object_local(const Vector3 &p_scale); void translate_object_local(const Vector3 &p_offset); - void global_rotate(const Vector3 &p_axis, float p_angle); + void global_rotate(const Vector3 &p_axis, real_t p_angle); void global_scale(const Vector3 &p_scale); void global_translate(const Vector3 &p_offset); diff --git a/scene/3d/occluder_instance_3d.cpp b/scene/3d/occluder_instance_3d.cpp index b0b9668fd2..f3e174c01b 100644 --- a/scene/3d/occluder_instance_3d.cpp +++ b/scene/3d/occluder_instance_3d.cpp @@ -173,11 +173,13 @@ void OccluderInstance3D::set_occluder(const Ref<Occluder3D> &p_occluder) { set_base(RID()); } - update_gizmo(); + update_gizmos(); + update_configuration_warnings(); } void OccluderInstance3D::_occluder_changed() { - update_gizmo(); + update_gizmos(); + update_configuration_warnings(); } Ref<Occluder3D> OccluderInstance3D::get_occluder() const { @@ -186,24 +188,29 @@ Ref<Occluder3D> OccluderInstance3D::get_occluder() const { void OccluderInstance3D::set_bake_mask(uint32_t p_mask) { bake_mask = p_mask; + update_configuration_warnings(); } uint32_t OccluderInstance3D::get_bake_mask() const { return bake_mask; } -void OccluderInstance3D::set_bake_mask_bit(int p_layer, bool p_enable) { - ERR_FAIL_INDEX(p_layer, 32); - if (p_enable) { - set_bake_mask(bake_mask | (1 << p_layer)); +void OccluderInstance3D::set_bake_mask_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Render layer number must be between 1 and 20 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 20, "Render layer number must be between 1 and 20 inclusive."); + uint32_t mask = get_bake_mask(); + if (p_value) { + mask |= 1 << (p_layer_number - 1); } else { - set_bake_mask(bake_mask & (~(1 << p_layer))); + mask &= ~(1 << (p_layer_number - 1)); } + set_bake_mask(mask); } -bool OccluderInstance3D::get_bake_mask_bit(int p_layer) const { - ERR_FAIL_INDEX_V(p_layer, 32, false); - return (bake_mask & (1 << p_layer)); +bool OccluderInstance3D::get_bake_mask_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Render layer number must be between 1 and 20 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 20, false, "Render layer number must be between 1 and 20 inclusive."); + return bake_mask & (1 << (p_layer_number - 1)); } bool OccluderInstance3D::_bake_material_check(Ref<Material> p_material) { @@ -314,11 +321,36 @@ OccluderInstance3D::BakeError OccluderInstance3D::bake(Node *p_from_node, String return BAKE_ERROR_OK; } +TypedArray<String> OccluderInstance3D::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); + + if (!bool(GLOBAL_GET("rendering/occlusion_culling/use_occlusion_culling"))) { + warnings.push_back(TTR("Occlusion culling is disabled in the Project Settings, which means occlusion culling won't be performed in the root viewport.\nTo resolve this, open the Project Settings and enable Rendering > Occlusion Culling > Use Occlusion Culling.")); + } + + if (bake_mask == 0) { + // NOTE: This warning will not be emitted if none of the 20 checkboxes + // exposed in the editor are checked. This is because there are + // currently 12 unexposed layers in the editor inspector. + warnings.push_back(TTR("The Bake Mask has no bits enabled, which means baking will not produce any occluder meshes for this OccluderInstance3D.\nTo resolve this, enable at least one bit in the Bake Mask property.")); + } + + if (occluder.is_null()) { + warnings.push_back(TTR("No occluder mesh is defined in the Occluder property, so no occlusion culling will be performed using this OccluderInstance3D.\nTo resolve this, select the OccluderInstance3D then use the Bake Occluders button at the top of the 3D editor viewport.")); + } else if (occluder->get_vertices().size() < 3) { + // Using the "New Occluder" dropdown button won't result in a correct occluder, + // so warn the user about this. + warnings.push_back(TTR("The occluder mesh has less than 3 vertices, so no occlusion culling will be performed using this OccluderInstance3D.\nTo generate a proper occluder mesh, select the OccluderInstance3D then use the Bake Occluders button at the top of the 3D editor viewport.")); + } + + return warnings; +} + void OccluderInstance3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_bake_mask", "mask"), &OccluderInstance3D::set_bake_mask); ClassDB::bind_method(D_METHOD("get_bake_mask"), &OccluderInstance3D::get_bake_mask); - ClassDB::bind_method(D_METHOD("set_bake_mask_bit", "layer", "enabled"), &OccluderInstance3D::set_bake_mask_bit); - ClassDB::bind_method(D_METHOD("get_bake_mask_bit", "layer"), &OccluderInstance3D::get_bake_mask_bit); + ClassDB::bind_method(D_METHOD("set_bake_mask_value", "layer_number", "value"), &OccluderInstance3D::set_bake_mask_value); + ClassDB::bind_method(D_METHOD("get_bake_mask_value", "layer_number"), &OccluderInstance3D::get_bake_mask_value); ClassDB::bind_method(D_METHOD("set_occluder", "occluder"), &OccluderInstance3D::set_occluder); ClassDB::bind_method(D_METHOD("get_occluder"), &OccluderInstance3D::get_occluder); diff --git a/scene/3d/occluder_instance_3d.h b/scene/3d/occluder_instance_3d.h index 4bb468274d..173614b80c 100644 --- a/scene/3d/occluder_instance_3d.h +++ b/scene/3d/occluder_instance_3d.h @@ -82,6 +82,8 @@ protected: static void _bind_methods(); public: + virtual TypedArray<String> get_configuration_warnings() const override; + enum BakeError { BAKE_ERROR_OK, BAKE_ERROR_NO_SAVE_PATH, @@ -97,8 +99,9 @@ public: void set_bake_mask(uint32_t p_mask); uint32_t get_bake_mask() const; - void set_bake_mask_bit(int p_layer, bool p_enable); - bool get_bake_mask_bit(int p_layer) const; + void set_bake_mask_value(int p_layer_number, bool p_enable); + bool get_bake_mask_value(int p_layer_number) const; + BakeError bake(Node *p_from_node, String p_occluder_path = ""); OccluderInstance3D(); diff --git a/scene/3d/path_3d.cpp b/scene/3d/path_3d.cpp index de115b35e3..9ea37e4bfa 100644 --- a/scene/3d/path_3d.cpp +++ b/scene/3d/path_3d.cpp @@ -30,18 +30,15 @@ #include "path_3d.h" -#include "core/config/engine.h" -#include "scene/scene_string_names.h" - void Path3D::_notification(int p_what) { } void Path3D::_curve_changed() { if (is_inside_tree() && Engine::get_singleton()->is_editor_hint()) { - update_gizmo(); + update_gizmos(); } if (is_inside_tree()) { - emit_signal("curve_changed"); + emit_signal(SNAME("curve_changed")); } // update the configuration warnings of all children of type PathFollow @@ -94,17 +91,24 @@ void PathFollow3D::_update_transform(bool p_update_xyz_rot) { return; } - float bl = c->get_baked_length(); + real_t bl = c->get_baked_length(); if (bl == 0.0) { return; } - float bi = c->get_bake_interval(); - float o_next = offset + bi; + real_t bi = c->get_bake_interval(); + real_t o_next = offset + bi; + real_t o_prev = offset - bi; if (loop) { o_next = Math::fposmod(o_next, bl); - } else if (rotation_mode == ROTATION_ORIENTED && o_next >= bl) { - o_next = bl; + o_prev = Math::fposmod(o_prev, bl); + } else if (rotation_mode == ROTATION_ORIENTED) { + if (o_next >= bl) { + o_next = bl; + } + if (o_prev <= 0) { + o_prev = 0; + } } Vector3 pos = c->interpolate_baked(offset, cubic); @@ -115,6 +119,11 @@ void PathFollow3D::_update_transform(bool p_update_xyz_rot) { if (rotation_mode == ROTATION_ORIENTED) { Vector3 forward = c->interpolate_baked(o_next, cubic) - pos; + // Try with the previous position + if (forward.length_squared() < CMP_EPSILON2) { + forward = pos - c->interpolate_baked(o_prev, cubic); + } + if (forward.length_squared() < CMP_EPSILON2) { forward = Vector3(0, 0, 1); } else { @@ -157,8 +166,8 @@ void PathFollow3D::_update_transform(bool p_update_xyz_rot) { Vector3 t_cur = (c->interpolate_baked(offset + delta_offset, cubic) - pos).normalized(); Vector3 axis = t_prev.cross(t_cur); - float dot = t_prev.dot(t_cur); - float angle = Math::acos(CLAMP(dot, -1, 1)); + real_t dot = t_prev.dot(t_cur); + real_t angle = Math::acos(CLAMP(dot, -1, 1)); if (likely(!Math::is_zero_approx(angle))) { if (rotation_mode == ROTATION_Y) { @@ -177,7 +186,7 @@ void PathFollow3D::_update_transform(bool p_update_xyz_rot) { } // do the additional tilting - float tilt_angle = c->interpolate_baked_tilt(offset); + real_t tilt_angle = c->interpolate_baked_tilt(offset); Vector3 tilt_axis = t_cur; // not sure what tilt is supposed to do, is this correct?? if (likely(!Math::is_zero_approx(Math::abs(tilt_angle)))) { @@ -232,7 +241,7 @@ bool PathFollow3D::get_cubic_interpolation() const { void PathFollow3D::_validate_property(PropertyInfo &property) const { if (property.name == "offset") { - float max = 10000; + real_t max = 10000; if (path && path->get_curve().is_valid()) { max = path->get_curve()->get_baked_length(); } @@ -295,13 +304,13 @@ void PathFollow3D::_bind_methods() { BIND_ENUM_CONSTANT(ROTATION_ORIENTED); } -void PathFollow3D::set_offset(float p_offset) { +void PathFollow3D::set_offset(real_t p_offset) { delta_offset = p_offset - offset; offset = p_offset; if (path) { if (path->get_curve().is_valid()) { - float path_length = path->get_curve()->get_baked_length(); + real_t path_length = path->get_curve()->get_baked_length(); if (loop) { offset = Math::fposmod(offset, path_length); @@ -317,39 +326,39 @@ void PathFollow3D::set_offset(float p_offset) { } } -void PathFollow3D::set_h_offset(float p_h_offset) { +void PathFollow3D::set_h_offset(real_t p_h_offset) { h_offset = p_h_offset; if (path) { _update_transform(); } } -float PathFollow3D::get_h_offset() const { +real_t PathFollow3D::get_h_offset() const { return h_offset; } -void PathFollow3D::set_v_offset(float p_v_offset) { +void PathFollow3D::set_v_offset(real_t p_v_offset) { v_offset = p_v_offset; if (path) { _update_transform(); } } -float PathFollow3D::get_v_offset() const { +real_t PathFollow3D::get_v_offset() const { return v_offset; } -float PathFollow3D::get_offset() const { +real_t PathFollow3D::get_offset() const { return offset; } -void PathFollow3D::set_unit_offset(float p_unit_offset) { +void PathFollow3D::set_unit_offset(real_t p_unit_offset) { if (path && path->get_curve().is_valid() && path->get_curve()->get_baked_length()) { set_offset(p_unit_offset * path->get_curve()->get_baked_length()); } } -float PathFollow3D::get_unit_offset() const { +real_t PathFollow3D::get_unit_offset() const { if (path && path->get_curve().is_valid() && path->get_curve()->get_baked_length()) { return get_offset() / path->get_curve()->get_baked_length(); } else { diff --git a/scene/3d/path_3d.h b/scene/3d/path_3d.h index 8545370a4a..1ffe291100 100644 --- a/scene/3d/path_3d.h +++ b/scene/3d/path_3d.h @@ -83,17 +83,17 @@ protected: static void _bind_methods(); public: - void set_offset(float p_offset); - float get_offset() const; + void set_offset(real_t p_offset); + real_t get_offset() const; - void set_h_offset(float p_h_offset); - float get_h_offset() const; + void set_h_offset(real_t p_h_offset); + real_t get_h_offset() const; - void set_v_offset(float p_v_offset); - float get_v_offset() const; + void set_v_offset(real_t p_v_offset); + real_t get_v_offset() const; - void set_unit_offset(float p_unit_offset); - float get_unit_offset() const; + void set_unit_offset(real_t p_unit_offset); + real_t get_unit_offset() const; void set_loop(bool p_loop); bool has_loop() const; diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp index e7482d35e7..00c6664e65 100644 --- a/scene/3d/physics_body_3d.cpp +++ b/scene/3d/physics_body_3d.cpp @@ -30,22 +30,16 @@ #include "physics_body_3d.h" -#include "core/config/engine.h" #include "core/core_string_names.h" -#include "core/object/class_db.h" -#include "core/templates/list.h" -#include "core/templates/rid.h" -#include "scene/3d/collision_shape_3d.h" #include "scene/scene_string_names.h" -#include "servers/navigation_server_3d.h" #ifdef TOOLS_ENABLED #include "editor/plugins/node_3d_editor_plugin.h" #endif void PhysicsBody3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("move_and_collide", "rel_vec", "infinite_inertia", "exclude_raycast_shapes", "test_only", "safe_margin"), &PhysicsBody3D::_move, DEFVAL(true), DEFVAL(true), DEFVAL(false), DEFVAL(0.001)); - ClassDB::bind_method(D_METHOD("test_move", "from", "rel_vec", "infinite_inertia", "exclude_raycast_shapes", "collision", "safe_margin"), &PhysicsBody3D::test_move, DEFVAL(true), DEFVAL(true), DEFVAL(Variant()), DEFVAL(0.001)); + ClassDB::bind_method(D_METHOD("move_and_collide", "rel_vec", "test_only", "safe_margin"), &PhysicsBody3D::_move, DEFVAL(false), DEFVAL(0.001)); + ClassDB::bind_method(D_METHOD("test_move", "from", "rel_vec", "collision", "safe_margin"), &PhysicsBody3D::test_move, DEFVAL(Variant()), DEFVAL(0.001)); ClassDB::bind_method(D_METHOD("set_axis_lock", "axis", "lock"), &PhysicsBody3D::set_axis_lock); ClassDB::bind_method(D_METHOD("get_axis_lock", "axis"), &PhysicsBody3D::get_axis_lock); @@ -78,8 +72,7 @@ TypedArray<PhysicsBody3D> PhysicsBody3D::get_collision_exceptions() { List<RID> exceptions; PhysicsServer3D::get_singleton()->body_get_collision_exceptions(get_rid(), &exceptions); Array ret; - for (List<RID>::Element *E = exceptions.front(); E; E = E->next()) { - RID body = E->get(); + for (const RID &body : exceptions) { ObjectID instance_id = PhysicsServer3D::get_singleton()->body_get_object_instance_id(body); Object *obj = ObjectDB::get_instance(instance_id); PhysicsBody3D *physics_body = Object::cast_to<PhysicsBody3D>(obj); @@ -102,9 +95,9 @@ void PhysicsBody3D::remove_collision_exception_with(Node *p_node) { PhysicsServer3D::get_singleton()->body_remove_collision_exception(get_rid(), collision_object->get_rid()); } -Ref<KinematicCollision3D> PhysicsBody3D::_move(const Vector3 &p_motion, bool p_infinite_inertia, bool p_exclude_raycast_shapes, bool p_test_only, real_t p_margin) { +Ref<KinematicCollision3D> PhysicsBody3D::_move(const Vector3 &p_motion, bool p_test_only, real_t p_margin) { PhysicsServer3D::MotionResult result; - if (move_and_collide(p_motion, p_infinite_inertia, result, p_margin, p_exclude_raycast_shapes, p_test_only)) { + if (move_and_collide(p_motion, result, p_margin, p_test_only)) { if (motion_cache.is_null()) { motion_cache.instantiate(); motion_cache->owner = this; @@ -118,18 +111,18 @@ Ref<KinematicCollision3D> PhysicsBody3D::_move(const Vector3 &p_motion, bool p_i return Ref<KinematicCollision3D>(); } -bool PhysicsBody3D::move_and_collide(const Vector3 &p_motion, bool p_infinite_inertia, PhysicsServer3D::MotionResult &r_result, real_t p_margin, bool p_exclude_raycast_shapes, bool p_test_only, bool p_cancel_sliding) { +bool PhysicsBody3D::move_and_collide(const Vector3 &p_motion, PhysicsServer3D::MotionResult &r_result, real_t p_margin, bool p_test_only, bool p_cancel_sliding, bool p_collide_separation_ray, const Set<RID> &p_exclude) { Transform3D gt = get_global_transform(); - bool colliding = PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_infinite_inertia, p_margin, &r_result, p_exclude_raycast_shapes); + bool colliding = PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_margin, &r_result, p_collide_separation_ray, p_exclude); // Restore direction of motion to be along original motion, // in order to avoid sliding due to recovery, // but only if collision depth is low enough to avoid tunneling. - real_t motion_length = p_motion.length(); - if (motion_length > CMP_EPSILON) { - real_t precision = CMP_EPSILON; + if (p_cancel_sliding) { + real_t motion_length = p_motion.length(); + real_t precision = 0.001; - if (colliding && p_cancel_sliding) { + if (colliding) { // Can't just use margin as a threshold because collision depth is calculated on unsafe motion, // so even in normal resting cases the depth can be a bit more than the margin. precision += motion_length * (r_result.collision_unsafe_fraction - r_result.collision_safe_fraction); @@ -140,36 +133,41 @@ bool PhysicsBody3D::move_and_collide(const Vector3 &p_motion, bool p_infinite_in } if (p_cancel_sliding) { + // When motion is null, recovery is the resulting motion. + Vector3 motion_normal; + if (motion_length > CMP_EPSILON) { + motion_normal = p_motion / motion_length; + } + // Check depth of recovery. - Vector3 motion_normal = p_motion / motion_length; - real_t dot = r_result.motion.dot(motion_normal); - Vector3 recovery = r_result.motion - motion_normal * dot; + real_t projected_length = r_result.travel.dot(motion_normal); + Vector3 recovery = r_result.travel - motion_normal * projected_length; real_t recovery_length = recovery.length(); // Fixes cases where canceling slide causes the motion to go too deep into the ground, - // Becauses we're only taking rest information into account and not general recovery. + // because we're only taking rest information into account and not general recovery. if (recovery_length < (real_t)p_margin + precision) { // Apply adjustment to motion. - r_result.motion = motion_normal * dot; - r_result.remainder = p_motion - r_result.motion; + r_result.travel = motion_normal * projected_length; + r_result.remainder = p_motion - r_result.travel; } } } for (int i = 0; i < 3; i++) { if (locked_axis & (1 << i)) { - r_result.motion[i] = 0; + r_result.travel[i] = 0; } } if (!p_test_only) { - gt.origin += r_result.motion; + gt.origin += r_result.travel; set_global_transform(gt); } return colliding; } -bool PhysicsBody3D::test_move(const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, bool p_exclude_raycast_shapes, const Ref<KinematicCollision3D> &r_collision, real_t p_margin) { +bool PhysicsBody3D::test_move(const Transform3D &p_from, const Vector3 &p_motion, const Ref<KinematicCollision3D> &r_collision, real_t p_margin) { ERR_FAIL_COND_V(!is_inside_tree(), false); PhysicsServer3D::MotionResult *r = nullptr; @@ -178,7 +176,7 @@ bool PhysicsBody3D::test_move(const Transform3D &p_from, const Vector3 &p_motion r = const_cast<PhysicsServer3D::MotionResult *>(&r_collision->result); } - return PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), p_from, p_motion, p_infinite_inertia, p_margin, r, p_exclude_raycast_shapes); + return PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), p_from, p_motion, p_margin, r); } void PhysicsBody3D::set_axis_lock(PhysicsServer3D::BodyAxis p_axis, bool p_lock) { @@ -225,44 +223,16 @@ Ref<PhysicsMaterial> StaticBody3D::get_physics_material_override() const { return physics_material_override; } -void StaticBody3D::set_kinematic_motion_enabled(bool p_enabled) { - if (p_enabled == kinematic_motion) { - return; - } - - kinematic_motion = p_enabled; - - if (kinematic_motion) { - set_body_mode(PhysicsServer3D::BODY_MODE_KINEMATIC); - } else { - set_body_mode(PhysicsServer3D::BODY_MODE_STATIC); - } - - _update_kinematic_motion(); -} - -bool StaticBody3D::is_kinematic_motion_enabled() const { - return kinematic_motion; -} - void StaticBody3D::set_constant_linear_velocity(const Vector3 &p_vel) { constant_linear_velocity = p_vel; - if (kinematic_motion) { - _update_kinematic_motion(); - } else { - PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_LINEAR_VELOCITY, constant_linear_velocity); - } + PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_LINEAR_VELOCITY, constant_linear_velocity); } void StaticBody3D::set_constant_angular_velocity(const Vector3 &p_vel) { constant_angular_velocity = p_vel; - if (kinematic_motion) { - _update_kinematic_motion(); - } else { - PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_ANGULAR_VELOCITY, constant_angular_velocity); - } + PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_ANGULAR_VELOCITY, constant_angular_velocity); } Vector3 StaticBody3D::get_constant_linear_velocity() const { @@ -273,81 +243,22 @@ Vector3 StaticBody3D::get_constant_angular_velocity() const { return constant_angular_velocity; } -Vector3 StaticBody3D::get_linear_velocity() const { - return linear_velocity; -} - -Vector3 StaticBody3D::get_angular_velocity() const { - return angular_velocity; -} - -void StaticBody3D::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - return; - } -#endif - - ERR_FAIL_COND(!kinematic_motion); - - real_t delta_time = get_physics_process_delta_time(); - - Transform3D new_transform = get_global_transform(); - new_transform.origin += constant_linear_velocity * delta_time; - - real_t ang_vel = constant_angular_velocity.length(); - if (!Math::is_zero_approx(ang_vel)) { - Vector3 ang_vel_axis = constant_angular_velocity / ang_vel; - Basis rot(ang_vel_axis, ang_vel * delta_time); - new_transform.basis = rot * new_transform.basis; - new_transform.orthonormalize(); - } - - PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_TRANSFORM, new_transform); - - // Propagate transform change to node. - set_ignore_transform_notification(true); - set_global_transform(new_transform); - set_ignore_transform_notification(false); - _on_transform_changed(); - } break; - } -} - void StaticBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_constant_linear_velocity", "vel"), &StaticBody3D::set_constant_linear_velocity); ClassDB::bind_method(D_METHOD("set_constant_angular_velocity", "vel"), &StaticBody3D::set_constant_angular_velocity); ClassDB::bind_method(D_METHOD("get_constant_linear_velocity"), &StaticBody3D::get_constant_linear_velocity); ClassDB::bind_method(D_METHOD("get_constant_angular_velocity"), &StaticBody3D::get_constant_angular_velocity); - ClassDB::bind_method(D_METHOD("set_kinematic_motion_enabled", "enabled"), &StaticBody3D::set_kinematic_motion_enabled); - ClassDB::bind_method(D_METHOD("is_kinematic_motion_enabled"), &StaticBody3D::is_kinematic_motion_enabled); - ClassDB::bind_method(D_METHOD("set_physics_material_override", "physics_material_override"), &StaticBody3D::set_physics_material_override); 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::BOOL, "kinematic_motion"), "set_kinematic_motion_enabled", "is_kinematic_motion_enabled"); } -void StaticBody3D::_direct_state_changed(Object *p_state) { -#ifdef DEBUG_ENABLED - PhysicsDirectBodyState3D *state = Object::cast_to<PhysicsDirectBodyState3D>(p_state); - ERR_FAIL_NULL_MSG(state, "Method '_direct_state_changed' must receive a valid PhysicsDirectBodyState3D object as argument"); -#else - PhysicsDirectBodyState3D *state = (PhysicsDirectBodyState3D *)p_state; //trust it -#endif - - linear_velocity = state->get_linear_velocity(); - angular_velocity = state->get_angular_velocity(); -} - -StaticBody3D::StaticBody3D() : - PhysicsBody3D(PhysicsServer3D::BODY_MODE_STATIC) { +StaticBody3D::StaticBody3D(PhysicsServer3D::BodyMode p_mode) : + PhysicsBody3D(p_mode) { } void StaticBody3D::_reload_physics_characteristics() { @@ -360,25 +271,97 @@ void StaticBody3D::_reload_physics_characteristics() { } } -void StaticBody3D::_update_kinematic_motion() { +Vector3 AnimatableBody3D::get_linear_velocity() const { + return linear_velocity; +} + +Vector3 AnimatableBody3D::get_angular_velocity() const { + return angular_velocity; +} + +void AnimatableBody3D::set_sync_to_physics(bool p_enable) { + if (sync_to_physics == p_enable) { + return; + } + + sync_to_physics = p_enable; + + _update_kinematic_motion(); +} + +bool AnimatableBody3D::is_sync_to_physics_enabled() const { + return sync_to_physics; +} + +void AnimatableBody3D::_update_kinematic_motion() { #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { return; } #endif - if (kinematic_motion) { - PhysicsServer3D::get_singleton()->body_set_force_integration_callback(get_rid(), callable_mp(this, &StaticBody3D::_direct_state_changed)); - - if (!constant_angular_velocity.is_equal_approx(Vector3()) || !constant_linear_velocity.is_equal_approx(Vector3())) { - set_physics_process_internal(true); - return; - } + if (sync_to_physics) { + set_only_update_transform_changes(true); + set_notify_local_transform(true); } else { - PhysicsServer3D::get_singleton()->body_set_force_integration_callback(get_rid(), Callable()); + set_only_update_transform_changes(false); + set_notify_local_transform(false); + } +} + +void AnimatableBody3D::_body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state) { + AnimatableBody3D *body = (AnimatableBody3D *)p_instance; + body->_body_state_changed(p_state); +} + +void AnimatableBody3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) { + linear_velocity = p_state->get_linear_velocity(); + angular_velocity = p_state->get_angular_velocity(); + + if (!sync_to_physics) { + return; } - set_physics_process_internal(false); + last_valid_transform = p_state->get_transform(); + set_notify_local_transform(false); + set_global_transform(last_valid_transform); + set_notify_local_transform(true); + _on_transform_changed(); +} + +void AnimatableBody3D::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + last_valid_transform = get_global_transform(); + } break; + + case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { + // Used by sync to physics, send the new transform to the physics... + Transform3D new_transform = get_global_transform(); + + PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_TRANSFORM, new_transform); + + // ... but then revert changes. + set_notify_local_transform(false); + set_global_transform(last_valid_transform); + set_notify_local_transform(true); + _on_transform_changed(); + } break; + } +} + +void AnimatableBody3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_sync_to_physics", "enable"), &AnimatableBody3D::set_sync_to_physics); + ClassDB::bind_method(D_METHOD("is_sync_to_physics_enabled"), &AnimatableBody3D::is_sync_to_physics_enabled); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync_to_physics"), "set_sync_to_physics", "is_sync_to_physics_enabled"); +} + +AnimatableBody3D::AnimatableBody3D() : + StaticBody3D(PhysicsServer3D::BODY_MODE_KINEMATIC) { + PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), this, _body_state_changed_callback); + + _update_kinematic_motion(); } void RigidBody3D::_body_enter_tree(ObjectID p_id) { @@ -493,26 +476,27 @@ struct _RigidBodyInOut { int local_shape = 0; }; -void RigidBody3D::_direct_state_changed(Object *p_state) { -#ifdef DEBUG_ENABLED - state = Object::cast_to<PhysicsDirectBodyState3D>(p_state); - ERR_FAIL_NULL_MSG(state, "Method '_direct_state_changed' must receive a valid PhysicsDirectBodyState3D object as argument"); -#else - state = (PhysicsDirectBodyState3D *)p_state; //trust it -#endif +void RigidBody3D::_body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state) { + RigidBody3D *body = (RigidBody3D *)p_instance; + body->_body_state_changed(p_state); +} +void RigidBody3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) { set_ignore_transform_notification(true); - set_global_transform(state->get_transform()); - linear_velocity = state->get_linear_velocity(); - angular_velocity = state->get_angular_velocity(); - inverse_inertia_tensor = state->get_inverse_inertia_tensor(); - if (sleeping != state->is_sleeping()) { - sleeping = state->is_sleeping(); + set_global_transform(p_state->get_transform()); + + linear_velocity = p_state->get_linear_velocity(); + angular_velocity = p_state->get_angular_velocity(); + + inverse_inertia_tensor = p_state->get_inverse_inertia_tensor(); + + if (sleeping != p_state->is_sleeping()) { + sleeping = p_state->is_sleeping(); emit_signal(SceneStringNames::get_singleton()->sleeping_state_changed); } - if (get_script_instance()) { - get_script_instance()->call("_integrate_forces", state); - } + + GDVIRTUAL_CALL(_integrate_forces, p_state); + set_ignore_transform_notification(false); _on_transform_changed(); @@ -528,18 +512,18 @@ void RigidBody3D::_direct_state_changed(Object *p_state) { } } - _RigidBodyInOut *toadd = (_RigidBodyInOut *)alloca(state->get_contact_count() * sizeof(_RigidBodyInOut)); + _RigidBodyInOut *toadd = (_RigidBodyInOut *)alloca(p_state->get_contact_count() * sizeof(_RigidBodyInOut)); int toadd_count = 0; //state->get_contact_count(); RigidBody3D_RemoveAction *toremove = (RigidBody3D_RemoveAction *)alloca(rc * sizeof(RigidBody3D_RemoveAction)); int toremove_count = 0; //put the ones to add - for (int i = 0; i < state->get_contact_count(); i++) { - RID rid = state->get_contact_collider(i); - ObjectID obj = state->get_contact_collider_id(i); - int local_shape = state->get_contact_local_shape(i); - int shape = state->get_contact_collider_shape(i); + for (int i = 0; i < p_state->get_contact_count(); i++) { + RID rid = p_state->get_contact_collider(i); + ObjectID obj = p_state->get_contact_collider_id(i); + int local_shape = p_state->get_contact_local_shape(i); + int shape = p_state->get_contact_collider_shape(i); //bool found=false; @@ -594,8 +578,6 @@ void RigidBody3D::_direct_state_changed(Object *p_state) { contact_monitor->locked = false; } - - state = nullptr; } void RigidBody3D::_notification(int p_what) { @@ -649,6 +631,60 @@ real_t RigidBody3D::get_mass() const { return mass; } +void RigidBody3D::set_inertia(const Vector3 &p_inertia) { + ERR_FAIL_COND(p_inertia.x < 0); + ERR_FAIL_COND(p_inertia.y < 0); + ERR_FAIL_COND(p_inertia.z < 0); + + inertia = p_inertia; + PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_INERTIA, inertia); +} + +const Vector3 &RigidBody3D::get_inertia() const { + return inertia; +} + +void RigidBody3D::set_center_of_mass_mode(CenterOfMassMode p_mode) { + if (center_of_mass_mode == p_mode) { + return; + } + + center_of_mass_mode = p_mode; + + switch (center_of_mass_mode) { + case CENTER_OF_MASS_MODE_AUTO: { + center_of_mass = Vector3(); + PhysicsServer3D::get_singleton()->body_reset_mass_properties(get_rid()); + if (inertia != Vector3()) { + PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_INERTIA, inertia); + } + } break; + + case CENTER_OF_MASS_MODE_CUSTOM: { + PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_CENTER_OF_MASS, center_of_mass); + } break; + } +} + +RigidBody3D::CenterOfMassMode RigidBody3D::get_center_of_mass_mode() const { + return center_of_mass_mode; +} + +void RigidBody3D::set_center_of_mass(const Vector3 &p_center_of_mass) { + if (center_of_mass == p_center_of_mass) { + return; + } + + ERR_FAIL_COND(center_of_mass_mode != CENTER_OF_MASS_MODE_CUSTOM); + center_of_mass = p_center_of_mass; + + PhysicsServer3D::get_singleton()->body_set_param(get_rid(), PhysicsServer3D::BODY_PARAM_CENTER_OF_MASS, center_of_mass); +} + +const Vector3 &RigidBody3D::get_center_of_mass() const { + return center_of_mass; +} + void RigidBody3D::set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override) { if (physics_material_override.is_valid()) { if (physics_material_override->is_connected(CoreStringNames::get_singleton()->changed, callable_mp(this, &RigidBody3D::_reload_physics_characteristics))) { @@ -698,25 +734,15 @@ real_t RigidBody3D::get_angular_damp() const { } void RigidBody3D::set_axis_velocity(const Vector3 &p_axis) { - Vector3 v = state ? state->get_linear_velocity() : linear_velocity; Vector3 axis = p_axis.normalized(); - v -= axis * axis.dot(v); - v += p_axis; - if (state) { - set_linear_velocity(v); - } else { - PhysicsServer3D::get_singleton()->body_set_axis_velocity(get_rid(), p_axis); - linear_velocity = v; - } + linear_velocity -= axis * axis.dot(linear_velocity); + linear_velocity += p_axis; + PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_LINEAR_VELOCITY, linear_velocity); } void RigidBody3D::set_linear_velocity(const Vector3 &p_velocity) { linear_velocity = p_velocity; - if (state) { - state->set_linear_velocity(linear_velocity); - } else { - PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_LINEAR_VELOCITY, linear_velocity); - } + PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_LINEAR_VELOCITY, linear_velocity); } Vector3 RigidBody3D::get_linear_velocity() const { @@ -725,11 +751,7 @@ Vector3 RigidBody3D::get_linear_velocity() const { void RigidBody3D::set_angular_velocity(const Vector3 &p_velocity) { angular_velocity = p_velocity; - if (state) { - state->set_angular_velocity(angular_velocity); - } else { - PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_ANGULAR_VELOCITY, angular_velocity); - } + PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_ANGULAR_VELOCITY, angular_velocity); } Vector3 RigidBody3D::get_angular_velocity() const { @@ -883,6 +905,15 @@ void RigidBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_mass", "mass"), &RigidBody3D::set_mass); ClassDB::bind_method(D_METHOD("get_mass"), &RigidBody3D::get_mass); + ClassDB::bind_method(D_METHOD("set_inertia", "inertia"), &RigidBody3D::set_inertia); + ClassDB::bind_method(D_METHOD("get_inertia"), &RigidBody3D::get_inertia); + + ClassDB::bind_method(D_METHOD("set_center_of_mass_mode", "mode"), &RigidBody3D::set_center_of_mass_mode); + ClassDB::bind_method(D_METHOD("get_center_of_mass_mode"), &RigidBody3D::get_center_of_mass_mode); + + ClassDB::bind_method(D_METHOD("set_center_of_mass", "center_of_mass"), &RigidBody3D::set_center_of_mass); + ClassDB::bind_method(D_METHOD("get_center_of_mass"), &RigidBody3D::get_center_of_mass); + ClassDB::bind_method(D_METHOD("set_physics_material_override", "physics_material_override"), &RigidBody3D::set_physics_material_override); ClassDB::bind_method(D_METHOD("get_physics_material_override"), &RigidBody3D::get_physics_material_override); @@ -933,10 +964,14 @@ void RigidBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_colliding_bodies"), &RigidBody3D::get_colliding_bodies); - BIND_VMETHOD(MethodInfo("_integrate_forces", PropertyInfo(Variant::OBJECT, "state", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsDirectBodyState3D"))); + GDVIRTUAL_BIND(_integrate_forces, "state"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Dynamic,Static,DynamicLocked,Kinematic"), "set_mode", "get_mode"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.01,65535,0.01,exp"), "set_mass", "get_mass"); + 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::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_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"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "custom_integrator"), "set_use_custom_integrator", "is_using_custom_integrator"); @@ -962,11 +997,22 @@ void RigidBody3D::_bind_methods() { BIND_ENUM_CONSTANT(MODE_STATIC); BIND_ENUM_CONSTANT(MODE_DYNAMIC_LOCKED); BIND_ENUM_CONSTANT(MODE_KINEMATIC); + + BIND_ENUM_CONSTANT(CENTER_OF_MASS_MODE_AUTO); + BIND_ENUM_CONSTANT(CENTER_OF_MASS_MODE_CUSTOM); +} + +void RigidBody3D::_validate_property(PropertyInfo &property) const { + if (center_of_mass_mode != CENTER_OF_MASS_MODE_CUSTOM) { + if (property.name == "center_of_mass") { + property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL; + } + } } RigidBody3D::RigidBody3D() : PhysicsBody3D(PhysicsServer3D::BODY_MODE_DYNAMIC) { - PhysicsServer3D::get_singleton()->body_set_force_integration_callback(get_rid(), callable_mp(this, &RigidBody3D::_direct_state_changed)); + PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), this, _body_state_changed_callback); } RigidBody3D::~RigidBody3D() { @@ -990,167 +1036,162 @@ void RigidBody3D::_reload_physics_characteristics() { //so, if you pass 45 as limit, avoid numerical precision errors when angle is 45. #define FLOOR_ANGLE_THRESHOLD 0.01 -void CharacterBody3D::move_and_slide() { - Vector3 body_velocity_normal = linear_velocity.normalized(); - +bool CharacterBody3D::move_and_slide() { bool was_on_floor = on_floor; + // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky + double delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time(); + for (int i = 0; i < 3; i++) { if (locked_axis & (1 << i)) { linear_velocity[i] = 0.0; } } - // Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky - Vector3 motion = (floor_velocity + linear_velocity) * (Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time()); + Vector3 current_floor_velocity = floor_velocity; + if ((on_floor || on_wall) && on_floor_body.is_valid()) { + //this approach makes sure there is less delay between the actual body velocity and the one we saved + PhysicsDirectBodyState3D *bs = PhysicsServer3D::get_singleton()->body_get_direct_state(on_floor_body); + if (bs) { + Transform3D gt = get_global_transform(); + Vector3 local_position = gt.origin - bs->get_transform().origin; + current_floor_velocity = bs->get_velocity_at_local_position(local_position); + } + } + motion_results.clear(); on_floor = false; - on_floor_body = RID(); on_ceiling = false; on_wall = false; - motion_results.clear(); floor_normal = Vector3(); floor_velocity = Vector3(); - // No sliding on first attempt to keep motion stable when possible. - bool sliding_enabled = false; - for (int iteration = 0; iteration < max_slides; ++iteration) { - PhysicsServer3D::MotionResult result; - bool found_collision = false; - - for (int i = 0; i < 2; ++i) { - bool collided; - if (i == 0) { //collide - collided = move_and_collide(motion, infinite_inertia, result, margin, true, false, !sliding_enabled); - if (!collided) { - motion = Vector3(); //clear because no collision happened and motion completed - } - } else { //separate raycasts (if any) - collided = separate_raycast_shapes(result); - if (collided) { - result.remainder = motion; //keep - result.motion = Vector3(); - } - } + if (!current_floor_velocity.is_equal_approx(Vector3()) && on_floor_body.is_valid()) { + PhysicsServer3D::MotionResult floor_result; + Set<RID> exclude; + exclude.insert(on_floor_body); + if (move_and_collide(current_floor_velocity * delta, floor_result, margin, false, false, false, exclude)) { + motion_results.push_back(floor_result); + _set_collision_direction(floor_result); + } + } - if (collided) { - found_collision = true; + on_floor_body = RID(); + Vector3 motion = linear_velocity * delta; - motion_results.push_back(result); + // No sliding on first attempt to keep floor motion stable when possible, + // when stop on slope is enabled. + bool sliding_enabled = !floor_stop_on_slope; - if (up_direction == Vector3()) { - //all is a wall - on_wall = true; + for (int iteration = 0; iteration < max_slides; ++iteration) { + PhysicsServer3D::MotionResult result; + bool collided = move_and_collide(motion, result, margin, false, !sliding_enabled); + if (collided) { + motion_results.push_back(result); + _set_collision_direction(result); + + if (on_floor && floor_stop_on_slope && (linear_velocity.normalized() + up_direction).length() < 0.01) { + Transform3D gt = get_global_transform(); + if (result.travel.length() > margin) { + gt.origin -= result.travel.slide(up_direction); } else { - if (Math::acos(result.collision_normal.dot(up_direction)) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor - - on_floor = true; - floor_normal = result.collision_normal; - on_floor_body = result.collider; - floor_velocity = result.collider_velocity; - - if (stop_on_slope) { - if ((body_velocity_normal + up_direction).length() < 0.01) { - Transform3D gt = get_global_transform(); - gt.origin -= result.motion.slide(up_direction); - set_global_transform(gt); - linear_velocity = Vector3(); - return; - } - } - } else if (Math::acos(result.collision_normal.dot(-up_direction)) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling - on_ceiling = true; - } else { - on_wall = true; - } + gt.origin -= result.travel; } + set_global_transform(gt); + linear_velocity = Vector3(); + motion = Vector3(); + break; + } - if (sliding_enabled || !on_floor) { - motion = result.remainder.slide(result.collision_normal); - linear_velocity = linear_velocity.slide(result.collision_normal); + if (result.remainder.is_equal_approx(Vector3())) { + motion = Vector3(); + break; + } - for (int j = 0; j < 3; j++) { - if (locked_axis & (1 << j)) { - linear_velocity[j] = 0.0; - } - } + if (sliding_enabled || !on_floor) { + Vector3 slide_motion = result.remainder.slide(result.collision_normal); + if (slide_motion.dot(linear_velocity) > 0.0) { + motion = slide_motion; } else { - motion = result.remainder; + motion = Vector3(); } + } else { + motion = result.remainder; } - - sliding_enabled = true; } - if (!found_collision || motion == Vector3()) { + sliding_enabled = true; + + if (!collided || motion.is_equal_approx(Vector3())) { break; } } - if (!was_on_floor || snap == Vector3()) { - return; - } - - // Apply snap. - Transform3D gt = get_global_transform(); - PhysicsServer3D::MotionResult result; - if (move_and_collide(snap, infinite_inertia, result, margin, false, true)) { - bool apply = true; - if (up_direction != Vector3()) { - if (Math::acos(result.collision_normal.dot(up_direction)) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { - on_floor = true; - floor_normal = result.collision_normal; - on_floor_body = result.collider; - floor_velocity = result.collider_velocity; - if (stop_on_slope) { - // move and collide may stray the object a bit because of pre un-stucking, - // so only ensure that motion happens on floor direction in this case. - result.motion = result.motion.project(up_direction); + if (was_on_floor && !on_floor && !snap.is_equal_approx(Vector3())) { + // Apply snap. + Transform3D gt = get_global_transform(); + PhysicsServer3D::MotionResult result; + if (move_and_collide(snap, result, margin, true, false, true)) { + bool apply = true; + if (up_direction != Vector3()) { + if (result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { + on_floor = true; + floor_normal = result.collision_normal; + on_floor_body = result.collider; + floor_velocity = result.collider_velocity; + if (floor_stop_on_slope) { + // move and collide may stray the object a bit because of pre un-stucking, + // so only ensure that motion happens on floor direction in this case. + if (result.travel.length() > margin) { + result.travel = result.travel.project(up_direction); + } else { + result.travel = Vector3(); + } + } + } else { + apply = false; //snapped with floor direction, but did not snap to a floor, do not snap. } - } else { - apply = false; //snapped with floor direction, but did not snap to a floor, do not snap. } - } - if (apply) { - gt.origin += result.motion; - set_global_transform(gt); + if (apply) { + gt.origin += result.travel; + set_global_transform(gt); + } } } -} -bool CharacterBody3D::separate_raycast_shapes(PhysicsServer3D::MotionResult &r_result) { - PhysicsServer3D::SeparationResult sep_res[8]; //max 8 rays - - Transform3D gt = get_global_transform(); - - Vector3 recover; - int hits = PhysicsServer3D::get_singleton()->body_test_ray_separation(get_rid(), gt, infinite_inertia, recover, sep_res, 8, margin); - int deepest = -1; - real_t deepest_depth; - for (int i = 0; i < hits; i++) { - if (deepest == -1 || sep_res[i].collision_depth > deepest_depth) { - deepest = i; - deepest_depth = sep_res[i].collision_depth; - } + if (!on_floor && !on_wall) { + // Add last platform velocity when just left a moving platform. + linear_velocity += current_floor_velocity; } - gt.origin += recover; - set_global_transform(gt); + // Reset the gravity accumulation when touching the ground. + if (on_floor && linear_velocity.dot(up_direction) <= 0) { + linear_velocity = linear_velocity.slide(up_direction); + } - if (deepest != -1) { - r_result.collider_id = sep_res[deepest].collider_id; - r_result.collider_metadata = sep_res[deepest].collider_metadata; - r_result.collider_shape = sep_res[deepest].collider_shape; - r_result.collider_velocity = sep_res[deepest].collider_velocity; - r_result.collision_point = sep_res[deepest].collision_point; - r_result.collision_normal = sep_res[deepest].collision_normal; - r_result.collision_local_shape = sep_res[deepest].collision_local_shape; - r_result.motion = recover; - r_result.remainder = Vector3(); + return motion_results.size() > 0; +} - return true; +void CharacterBody3D::_set_collision_direction(const PhysicsServer3D::MotionResult &p_result) { + if (up_direction == Vector3()) { + //all is a wall + on_wall = true; } else { - return false; + if (p_result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor + on_floor = true; + floor_normal = p_result.collision_normal; + on_floor_body = p_result.collider; + floor_velocity = p_result.collider_velocity; + } else if (p_result.get_angle(-up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling + on_ceiling = true; + } else { + on_wall = true; + // Don't apply wall velocity when the collider is a CharacterBody3D. + if (Object::cast_to<CharacterBody3D>(ObjectDB::get_instance(p_result.collider_id)) == nullptr) { + on_floor_body = p_result.collider; + floor_velocity = p_result.collider_velocity; + } + } } } @@ -1174,23 +1215,40 @@ bool CharacterBody3D::is_on_floor() const { return on_floor; } +bool CharacterBody3D::is_on_floor_only() const { + return on_floor && !on_wall && !on_ceiling; +} + bool CharacterBody3D::is_on_wall() const { return on_wall; } +bool CharacterBody3D::is_on_wall_only() const { + return on_wall && !on_floor && !on_ceiling; +} + bool CharacterBody3D::is_on_ceiling() const { return on_ceiling; } +bool CharacterBody3D::is_on_ceiling_only() const { + return on_ceiling && !on_floor && !on_wall; +} + Vector3 CharacterBody3D::get_floor_normal() const { return floor_normal; } -Vector3 CharacterBody3D::get_floor_velocity() const { +real_t CharacterBody3D::get_floor_angle(const Vector3 &p_up_direction) const { + ERR_FAIL_COND_V(p_up_direction == Vector3(), 0); + return Math::acos(floor_normal.dot(p_up_direction)); +} + +Vector3 CharacterBody3D::get_platform_velocity() const { return floor_velocity; } -int CharacterBody3D::get_slide_count() const { +int CharacterBody3D::get_slide_collision_count() const { return motion_results.size(); } @@ -1214,19 +1272,19 @@ Ref<KinematicCollision3D> CharacterBody3D::_get_slide_collision(int p_bounce) { return slide_colliders[p_bounce]; } -bool CharacterBody3D::is_stop_on_slope_enabled() const { - return stop_on_slope; +Ref<KinematicCollision3D> CharacterBody3D::_get_last_slide_collision() { + if (motion_results.size() == 0) { + return Ref<KinematicCollision3D>(); + } + return _get_slide_collision(motion_results.size() - 1); } -void CharacterBody3D::set_stop_on_slope_enabled(bool p_enabled) { - stop_on_slope = p_enabled; +bool CharacterBody3D::is_floor_stop_on_slope_enabled() const { + return floor_stop_on_slope; } -bool CharacterBody3D::is_infinite_inertia_enabled() const { - return infinite_inertia; -} -void CharacterBody3D::set_infinite_inertia_enabled(bool p_enabled) { - infinite_inertia = p_enabled; +void CharacterBody3D::set_floor_stop_on_slope_enabled(bool p_enabled) { + floor_stop_on_slope = p_enabled; } int CharacterBody3D::get_max_slides() const { @@ -1234,7 +1292,7 @@ int CharacterBody3D::get_max_slides() const { } void CharacterBody3D::set_max_slides(int p_max_slides) { - ERR_FAIL_COND(p_max_slides > 0); + ERR_FAIL_COND(p_max_slides < 1); max_slides = p_max_slides; } @@ -1284,10 +1342,8 @@ void CharacterBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_safe_margin", "pixels"), &CharacterBody3D::set_safe_margin); ClassDB::bind_method(D_METHOD("get_safe_margin"), &CharacterBody3D::get_safe_margin); - ClassDB::bind_method(D_METHOD("is_stop_on_slope_enabled"), &CharacterBody3D::is_stop_on_slope_enabled); - ClassDB::bind_method(D_METHOD("set_stop_on_slope_enabled", "enabled"), &CharacterBody3D::set_stop_on_slope_enabled); - ClassDB::bind_method(D_METHOD("is_infinite_inertia_enabled"), &CharacterBody3D::is_infinite_inertia_enabled); - ClassDB::bind_method(D_METHOD("set_infinite_inertia_enabled", "enabled"), &CharacterBody3D::set_infinite_inertia_enabled); + ClassDB::bind_method(D_METHOD("is_floor_stop_on_slope_enabled"), &CharacterBody3D::is_floor_stop_on_slope_enabled); + ClassDB::bind_method(D_METHOD("set_floor_stop_on_slope_enabled", "enabled"), &CharacterBody3D::set_floor_stop_on_slope_enabled); ClassDB::bind_method(D_METHOD("get_max_slides"), &CharacterBody3D::get_max_slides); ClassDB::bind_method(D_METHOD("set_max_slides", "max_slides"), &CharacterBody3D::set_max_slides); ClassDB::bind_method(D_METHOD("get_floor_max_angle"), &CharacterBody3D::get_floor_max_angle); @@ -1298,21 +1354,26 @@ void CharacterBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_up_direction", "up_direction"), &CharacterBody3D::set_up_direction); ClassDB::bind_method(D_METHOD("is_on_floor"), &CharacterBody3D::is_on_floor); + ClassDB::bind_method(D_METHOD("is_on_floor_only"), &CharacterBody3D::is_on_floor_only); ClassDB::bind_method(D_METHOD("is_on_ceiling"), &CharacterBody3D::is_on_ceiling); + ClassDB::bind_method(D_METHOD("is_on_ceiling_only"), &CharacterBody3D::is_on_ceiling_only); ClassDB::bind_method(D_METHOD("is_on_wall"), &CharacterBody3D::is_on_wall); + ClassDB::bind_method(D_METHOD("is_on_wall_only"), &CharacterBody3D::is_on_wall_only); ClassDB::bind_method(D_METHOD("get_floor_normal"), &CharacterBody3D::get_floor_normal); - ClassDB::bind_method(D_METHOD("get_floor_velocity"), &CharacterBody3D::get_floor_velocity); + ClassDB::bind_method(D_METHOD("get_floor_angle", "up_direction"), &CharacterBody3D::get_floor_angle, DEFVAL(Vector3(0.0, 1.0, 0.0))); + ClassDB::bind_method(D_METHOD("get_platform_velocity"), &CharacterBody3D::get_platform_velocity); - ClassDB::bind_method(D_METHOD("get_slide_count"), &CharacterBody3D::get_slide_count); + ClassDB::bind_method(D_METHOD("get_slide_collision_count"), &CharacterBody3D::get_slide_collision_count); ClassDB::bind_method(D_METHOD("get_slide_collision", "slide_idx"), &CharacterBody3D::_get_slide_collision); + ClassDB::bind_method(D_METHOD("get_last_slide_collision"), &CharacterBody3D::_get_last_slide_collision); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "linear_velocity"), "set_linear_velocity", "get_linear_velocity"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stop_on_slope"), "set_stop_on_slope_enabled", "is_stop_on_slope_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "infinite_inertia"), "set_infinite_inertia_enabled", "is_infinite_inertia_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "max_slides"), "set_max_slides", "get_max_slides"); - 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::INT, "max_slides", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_max_slides", "get_max_slides"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "snap"), "set_snap", "get_snap"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "up_direction"), "set_up_direction", "get_up_direction"); + ADD_GROUP("Floor", "floor_"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_max_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians"), "set_floor_max_angle", "get_floor_max_angle"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "floor_stop_on_slope"), "set_floor_stop_on_slope_enabled", "is_floor_stop_on_slope_enabled"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision/safe_margin", PROPERTY_HINT_RANGE, "0.001,256,0.001"), "set_safe_margin", "get_safe_margin"); } @@ -1339,13 +1400,18 @@ Vector3 KinematicCollision3D::get_normal() const { } Vector3 KinematicCollision3D::get_travel() const { - return result.motion; + return result.travel; } Vector3 KinematicCollision3D::get_remainder() const { return result.remainder; } +real_t KinematicCollision3D::get_angle(const Vector3 &p_up_direction) const { + ERR_FAIL_COND_V(p_up_direction == Vector3(), 0); + return result.get_angle(p_up_direction); +} + Object *KinematicCollision3D::get_local_shape() const { if (!owner) { return nullptr; @@ -1400,6 +1466,7 @@ void KinematicCollision3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_normal"), &KinematicCollision3D::get_normal); ClassDB::bind_method(D_METHOD("get_travel"), &KinematicCollision3D::get_travel); ClassDB::bind_method(D_METHOD("get_remainder"), &KinematicCollision3D::get_remainder); + ClassDB::bind_method(D_METHOD("get_angle", "up_direction"), &KinematicCollision3D::get_angle, DEFVAL(Vector3(0.0, 1.0, 0.0))); ClassDB::bind_method(D_METHOD("get_local_shape"), &KinematicCollision3D::get_local_shape); ClassDB::bind_method(D_METHOD("get_collider"), &KinematicCollision3D::get_collider); ClassDB::bind_method(D_METHOD("get_collider_id"), &KinematicCollision3D::get_collider_id); @@ -2067,9 +2134,7 @@ bool PhysicalBone3D::_set(const StringName &p_name, const Variant &p_value) { if (joint_data) { if (joint_data->_set(p_name, p_value, joint)) { #ifdef TOOLS_ENABLED - if (get_gizmo().is_valid()) { - get_gizmo()->redraw(); - } + update_gizmos(); #endif return true; } @@ -2129,7 +2194,6 @@ void PhysicalBone3D::_notification(int p_what) { if (parent_skeleton) { if (-1 != bone_id) { parent_skeleton->unbind_physical_bone_from_bone(bone_id); - parent_skeleton->unbind_child_node_from_bone(bone_id, this); bone_id = -1; } } @@ -2145,23 +2209,19 @@ void PhysicalBone3D::_notification(int p_what) { } } -void PhysicalBone3D::_direct_state_changed(Object *p_state) { +void PhysicalBone3D::_body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state) { + PhysicalBone3D *bone = (PhysicalBone3D *)p_instance; + bone->_body_state_changed(p_state); +} + +void PhysicalBone3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) { if (!simulate_physics || !_internal_simulate_physics) { return; } - /// Update bone transform - - PhysicsDirectBodyState3D *state; - -#ifdef DEBUG_ENABLED - state = Object::cast_to<PhysicsDirectBodyState3D>(p_state); - ERR_FAIL_NULL_MSG(state, "Method '_direct_state_changed' must receive a valid PhysicsDirectBodyState3D object as argument"); -#else - state = (PhysicsDirectBodyState3D *)p_state; //trust it -#endif + /// Update bone transform. - Transform3D global_transform(state->get_transform()); + Transform3D global_transform(p_state->get_transform()); set_ignore_transform_notification(true); set_global_transform(global_transform); @@ -2225,7 +2285,7 @@ void PhysicalBone3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "body_offset"), "set_body_offset", "get_body_offset"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_RANGE, "0.01,65535,0.01,exp"), "set_mass", "get_mass"); + 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, "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"); @@ -2257,9 +2317,7 @@ void PhysicalBone3D::_update_joint_offset() { set_ignore_transform_notification(false); #ifdef TOOLS_ENABLED - if (get_gizmo().is_valid()) { - get_gizmo()->redraw(); - } + update_gizmos(); #endif } @@ -2426,9 +2484,7 @@ void PhysicalBone3D::set_joint_type(JointType p_joint_type) { #ifdef TOOLS_ENABLED notify_property_list_changed(); - if (get_gizmo().is_valid()) { - get_gizmo()->redraw(); - } + update_gizmos(); #endif } @@ -2590,7 +2646,6 @@ void PhysicalBone3D::update_bone_id() { if (-1 != bone_id) { // Assert the unbind from old node parent_skeleton->unbind_physical_bone_from_bone(bone_id); - parent_skeleton->unbind_child_node_from_bone(bone_id, this); } bone_id = new_bone_id; @@ -2628,7 +2683,7 @@ void PhysicalBone3D::_start_physics_simulation() { set_body_mode(PhysicsServer3D::BODY_MODE_DYNAMIC); PhysicsServer3D::get_singleton()->body_set_collision_layer(get_rid(), get_collision_layer()); PhysicsServer3D::get_singleton()->body_set_collision_mask(get_rid(), get_collision_mask()); - PhysicsServer3D::get_singleton()->body_set_force_integration_callback(get_rid(), callable_mp(this, &PhysicalBone3D::_direct_state_changed)); + PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), this, _body_state_changed_callback); set_as_top_level(true); _internal_simulate_physics = true; } @@ -2647,7 +2702,7 @@ void PhysicalBone3D::_stop_physics_simulation() { PhysicsServer3D::get_singleton()->body_set_collision_mask(get_rid(), 0); } if (_internal_simulate_physics) { - PhysicsServer3D::get_singleton()->body_set_force_integration_callback(get_rid(), Callable()); + PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), nullptr, nullptr); parent_skeleton->set_bone_global_pose_override(bone_id, Transform3D(), 0.0, false); set_as_top_level(false); _internal_simulate_physics = false; diff --git a/scene/3d/physics_body_3d.h b/scene/3d/physics_body_3d.h index 9d45ce3799..8e6463f838 100644 --- a/scene/3d/physics_body_3d.h +++ b/scene/3d/physics_body_3d.h @@ -50,11 +50,11 @@ protected: uint16_t locked_axis = 0; - Ref<KinematicCollision3D> _move(const Vector3 &p_motion, bool p_infinite_inertia = true, bool p_exclude_raycast_shapes = true, bool p_test_only = false, real_t p_margin = 0.001); + Ref<KinematicCollision3D> _move(const Vector3 &p_motion, bool p_test_only = false, real_t p_margin = 0.001); public: - bool move_and_collide(const Vector3 &p_motion, bool p_infinite_inertia, PhysicsServer3D::MotionResult &r_result, real_t p_margin, bool p_exclude_raycast_shapes = true, bool p_test_only = false, bool p_cancel_sliding = true); - bool test_move(const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia = true, bool p_exclude_raycast_shapes = true, const Ref<KinematicCollision3D> &r_collision = Ref<KinematicCollision3D>(), real_t p_margin = 0.001); + bool move_and_collide(const Vector3 &p_motion, PhysicsServer3D::MotionResult &r_result, real_t p_margin, bool p_test_only = false, bool p_cancel_sliding = true, bool p_collide_separation_ray = false, const Set<RID> &p_exclude = Set<RID>()); + bool test_move(const Transform3D &p_from, const Vector3 &p_motion, const Ref<KinematicCollision3D> &r_collision = Ref<KinematicCollision3D>(), real_t p_margin = 0.001); void set_axis_lock(PhysicsServer3D::BodyAxis p_axis, bool p_lock); bool get_axis_lock(PhysicsServer3D::BodyAxis p_axis) const; @@ -73,22 +73,15 @@ public: class StaticBody3D : public PhysicsBody3D { GDCLASS(StaticBody3D, PhysicsBody3D); +private: Vector3 constant_linear_velocity; Vector3 constant_angular_velocity; - Vector3 linear_velocity; - Vector3 angular_velocity; - Ref<PhysicsMaterial> physics_material_override; - bool kinematic_motion = false; - protected: - void _notification(int p_what); static void _bind_methods(); - void _direct_state_changed(Object *p_state); - public: void set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override); Ref<PhysicsMaterial> get_physics_material_override() const; @@ -99,18 +92,41 @@ public: Vector3 get_constant_linear_velocity() const; Vector3 get_constant_angular_velocity() const; + StaticBody3D(PhysicsServer3D::BodyMode p_mode = PhysicsServer3D::BODY_MODE_STATIC); + +private: + void _reload_physics_characteristics(); +}; + +class AnimatableBody3D : public StaticBody3D { + GDCLASS(AnimatableBody3D, StaticBody3D); + +private: + Vector3 linear_velocity; + Vector3 angular_velocity; + + bool sync_to_physics = false; + + Transform3D last_valid_transform; + + static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state); + void _body_state_changed(PhysicsDirectBodyState3D *p_state); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: virtual Vector3 get_linear_velocity() const override; virtual Vector3 get_angular_velocity() const override; - StaticBody3D(); + AnimatableBody3D(); private: - void _reload_physics_characteristics(); - void _update_kinematic_motion(); - void set_kinematic_motion_enabled(bool p_enabled); - bool is_kinematic_motion_enabled() const; + void set_sync_to_physics(bool p_enable); + bool is_sync_to_physics_enabled() const; }; class RigidBody3D : public PhysicsBody3D { @@ -124,12 +140,22 @@ public: MODE_KINEMATIC, }; + enum CenterOfMassMode { + CENTER_OF_MASS_MODE_AUTO, + CENTER_OF_MASS_MODE_CUSTOM, + }; + + GDVIRTUAL1(_integrate_forces, PhysicsDirectBodyState3D *) + protected: bool can_sleep = true; - PhysicsDirectBodyState3D *state = nullptr; Mode mode = MODE_DYNAMIC; real_t mass = 1.0; + Vector3 inertia; + CenterOfMassMode center_of_mass_mode = CENTER_OF_MASS_MODE_AUTO; + Vector3 center_of_mass; + Ref<PhysicsMaterial> physics_material_override; Vector3 linear_velocity; @@ -187,11 +213,14 @@ protected: void _body_exit_tree(ObjectID p_id); void _body_inout(int p_status, const RID &p_body, ObjectID p_instance, int p_body_shape, int p_local_shape); - virtual void _direct_state_changed(Object *p_state); + static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state); + virtual void _body_state_changed(PhysicsDirectBodyState3D *p_state); void _notification(int p_what); static void _bind_methods(); + virtual void _validate_property(PropertyInfo &property) const override; + public: void set_mode(Mode p_mode); Mode get_mode() const; @@ -201,6 +230,15 @@ public: virtual real_t get_inverse_mass() const override { return 1.0 / mass; } + void set_inertia(const Vector3 &p_inertia); + const Vector3 &get_inertia() const; + + void set_center_of_mass_mode(CenterOfMassMode p_mode); + CenterOfMassMode get_center_of_mass_mode() const; + + void set_center_of_mass(const Vector3 &p_center_of_mass); + const Vector3 &get_center_of_mass() const; + void set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override); Ref<PhysicsMaterial> get_physics_material_override() const; @@ -251,7 +289,7 @@ public: void apply_impulse(const Vector3 &p_impulse, const Vector3 &p_position = Vector3()); void apply_torque_impulse(const Vector3 &p_impulse); - TypedArray<String> get_configuration_warnings() const override; + virtual TypedArray<String> get_configuration_warnings() const override; RigidBody3D(); ~RigidBody3D(); @@ -261,6 +299,7 @@ private: }; VARIANT_ENUM_CAST(RigidBody3D::Mode); +VARIANT_ENUM_CAST(RigidBody3D::CenterOfMassMode); class KinematicCollision3D; @@ -270,8 +309,7 @@ class CharacterBody3D : public PhysicsBody3D { private: real_t margin = 0.001; - bool stop_on_slope = false; - bool infinite_inertia = true; + bool floor_stop_on_slope = false; int max_slides = 4; real_t floor_max_angle = Math::deg2rad((real_t)45.0); Vector3 snap; @@ -289,17 +327,15 @@ private: Vector<Ref<KinematicCollision3D>> slide_colliders; Ref<KinematicCollision3D> _get_slide_collision(int p_bounce); + Ref<KinematicCollision3D> _get_last_slide_collision(); - bool separate_raycast_shapes(PhysicsServer3D::MotionResult &r_result); + void _set_collision_direction(const PhysicsServer3D::MotionResult &p_result); void set_safe_margin(real_t p_margin); real_t get_safe_margin() const; - bool is_stop_on_slope_enabled() const; - void set_stop_on_slope_enabled(bool p_enabled); - - bool is_infinite_inertia_enabled() const; - void set_infinite_inertia_enabled(bool p_enabled); + bool is_floor_stop_on_slope_enabled() const; + void set_floor_stop_on_slope_enabled(bool p_enabled); int get_max_slides() const; void set_max_slides(int p_max_slides); @@ -318,18 +354,22 @@ protected: static void _bind_methods(); public: - void move_and_slide(); + bool move_and_slide(); virtual Vector3 get_linear_velocity() const override; void set_linear_velocity(const Vector3 &p_velocity); bool is_on_floor() const; + bool is_on_floor_only() const; bool is_on_wall() const; + bool is_on_wall_only() const; bool is_on_ceiling() const; + bool is_on_ceiling_only() const; Vector3 get_floor_normal() const; - Vector3 get_floor_velocity() const; + real_t get_floor_angle(const Vector3 &p_up_direction = Vector3(0.0, 1.0, 0.0)) const; + Vector3 get_platform_velocity() const; - int get_slide_count() const; + int get_slide_collision_count() const; PhysicsServer3D::MotionResult get_slide_collision(int p_bounce) const; CharacterBody3D(); @@ -352,6 +392,7 @@ public: Vector3 get_normal() const; Vector3 get_travel() const; Vector3 get_remainder() const; + real_t get_angle(const Vector3 &p_up_direction = Vector3(0.0, 1.0, 0.0)) const; Object *get_local_shape() const; Object *get_collider() const; ObjectID get_collider_id() const; @@ -513,7 +554,8 @@ protected: bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List<PropertyInfo> *p_list) const; void _notification(int p_what); - void _direct_state_changed(Object *p_state); + static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state); + void _body_state_changed(PhysicsDirectBodyState3D *p_state); static void _bind_methods(); diff --git a/scene/3d/physics_joint_3d.cpp b/scene/3d/physics_joint_3d.cpp index 01f10c171f..12938946a0 100644 --- a/scene/3d/physics_joint_3d.cpp +++ b/scene/3d/physics_joint_3d.cpp @@ -259,11 +259,11 @@ real_t PinJoint3D::get_param(Param p_param) const { void PinJoint3D::_configure_joint(RID p_joint, PhysicsBody3D *body_a, PhysicsBody3D *body_b) { Vector3 pinpos = get_global_transform().origin; - Vector3 local_a = body_a->get_global_transform().affine_inverse().xform(pinpos); + Vector3 local_a = body_a->to_local(pinpos); Vector3 local_b; if (body_b) { - local_b = body_b->get_global_transform().affine_inverse().xform(pinpos); + local_b = body_b->to_local(pinpos); } else { local_b = pinpos; } @@ -348,7 +348,7 @@ void HingeJoint3D::set_param(Param p_param, real_t p_value) { PhysicsServer3D::get_singleton()->hinge_joint_set_param(get_joint(), PhysicsServer3D::HingeJointParam(p_param), p_value); } - update_gizmo(); + update_gizmos(); } real_t HingeJoint3D::get_param(Param p_param) const { @@ -363,7 +363,7 @@ void HingeJoint3D::set_flag(Flag p_flag, bool p_value) { PhysicsServer3D::get_singleton()->hinge_joint_set_flag(get_joint(), PhysicsServer3D::HingeJointFlag(p_flag), p_value); } - update_gizmo(); + update_gizmos(); } bool HingeJoint3D::get_flag(Flag p_flag) const { @@ -497,7 +497,7 @@ void SliderJoint3D::set_param(Param p_param, real_t p_value) { if (is_configured()) { PhysicsServer3D::get_singleton()->slider_joint_set_param(get_joint(), PhysicsServer3D::SliderJointParam(p_param), p_value); } - update_gizmo(); + update_gizmos(); } real_t SliderJoint3D::get_param(Param p_param) const { @@ -602,7 +602,7 @@ void ConeTwistJoint3D::set_param(Param p_param, real_t p_value) { PhysicsServer3D::get_singleton()->cone_twist_joint_set_param(get_joint(), PhysicsServer3D::ConeTwistJointParam(p_param), p_value); } - update_gizmo(); + update_gizmos(); } real_t ConeTwistJoint3D::get_param(Param p_param) const { @@ -857,7 +857,7 @@ void Generic6DOFJoint3D::set_param_x(Param p_param, real_t p_value) { PhysicsServer3D::get_singleton()->generic_6dof_joint_set_param(get_joint(), Vector3::AXIS_X, PhysicsServer3D::G6DOFJointAxisParam(p_param), p_value); } - update_gizmo(); + update_gizmos(); } real_t Generic6DOFJoint3D::get_param_x(Param p_param) const { @@ -871,7 +871,7 @@ void Generic6DOFJoint3D::set_param_y(Param p_param, real_t p_value) { if (is_configured()) { PhysicsServer3D::get_singleton()->generic_6dof_joint_set_param(get_joint(), Vector3::AXIS_Y, PhysicsServer3D::G6DOFJointAxisParam(p_param), p_value); } - update_gizmo(); + update_gizmos(); } real_t Generic6DOFJoint3D::get_param_y(Param p_param) const { @@ -885,7 +885,7 @@ void Generic6DOFJoint3D::set_param_z(Param p_param, real_t p_value) { if (is_configured()) { PhysicsServer3D::get_singleton()->generic_6dof_joint_set_param(get_joint(), Vector3::AXIS_Z, PhysicsServer3D::G6DOFJointAxisParam(p_param), p_value); } - update_gizmo(); + update_gizmos(); } real_t Generic6DOFJoint3D::get_param_z(Param p_param) const { @@ -899,7 +899,7 @@ void Generic6DOFJoint3D::set_flag_x(Flag p_flag, bool p_enabled) { if (is_configured()) { PhysicsServer3D::get_singleton()->generic_6dof_joint_set_flag(get_joint(), Vector3::AXIS_X, PhysicsServer3D::G6DOFJointAxisFlag(p_flag), p_enabled); } - update_gizmo(); + update_gizmos(); } bool Generic6DOFJoint3D::get_flag_x(Flag p_flag) const { @@ -913,7 +913,7 @@ void Generic6DOFJoint3D::set_flag_y(Flag p_flag, bool p_enabled) { if (is_configured()) { PhysicsServer3D::get_singleton()->generic_6dof_joint_set_flag(get_joint(), Vector3::AXIS_Y, PhysicsServer3D::G6DOFJointAxisFlag(p_flag), p_enabled); } - update_gizmo(); + update_gizmos(); } bool Generic6DOFJoint3D::get_flag_y(Flag p_flag) const { @@ -927,7 +927,7 @@ void Generic6DOFJoint3D::set_flag_z(Flag p_flag, bool p_enabled) { if (is_configured()) { PhysicsServer3D::get_singleton()->generic_6dof_joint_set_flag(get_joint(), Vector3::AXIS_Z, PhysicsServer3D::G6DOFJointAxisFlag(p_flag), p_enabled); } - update_gizmo(); + update_gizmos(); } bool Generic6DOFJoint3D::get_flag_z(Flag p_flag) const { diff --git a/scene/3d/position_3d.cpp b/scene/3d/position_3d.cpp index b231ba0df7..9747465103 100644 --- a/scene/3d/position_3d.cpp +++ b/scene/3d/position_3d.cpp @@ -29,7 +29,6 @@ /*************************************************************************/ #include "position_3d.h" -#include "scene/resources/mesh.h" Position3D::Position3D() { } diff --git a/scene/3d/proximity_group_3d.cpp b/scene/3d/proximity_group_3d.cpp index 9d9fea68b0..c8c61a9f00 100644 --- a/scene/3d/proximity_group_3d.cpp +++ b/scene/3d/proximity_group_3d.cpp @@ -128,9 +128,10 @@ void ProximityGroup3D::broadcast(String p_method, Variant p_parameters) { void ProximityGroup3D::_proximity_group_broadcast(String p_method, Variant p_parameters) { if (dispatch_mode == MODE_PROXY) { + ERR_FAIL_COND(!is_inside_tree()); get_parent()->call(p_method, p_parameters); } else { - emit_signal("broadcast", p_method, p_parameters); + emit_signal(SNAME("broadcast"), p_method, p_parameters); } } diff --git a/scene/3d/proximity_group_3d.h b/scene/3d/proximity_group_3d.h index 05aa00b228..e45adc3040 100644 --- a/scene/3d/proximity_group_3d.h +++ b/scene/3d/proximity_group_3d.h @@ -49,7 +49,7 @@ private: DispatchMode dispatch_mode = MODE_PROXY; Vector3 grid_radius = Vector3(1, 1, 1); - float cell_size = 1.0; + real_t cell_size = 1.0; uint32_t group_version = 0; void _clear_groups(); diff --git a/scene/3d/ray_cast_3d.cpp b/scene/3d/ray_cast_3d.cpp index dfab3d4a17..fd4c6e7416 100644 --- a/scene/3d/ray_cast_3d.cpp +++ b/scene/3d/ray_cast_3d.cpp @@ -31,13 +31,11 @@ #include "ray_cast_3d.h" #include "collision_object_3d.h" -#include "core/config/engine.h" #include "mesh_instance_3d.h" -#include "servers/physics_server_3d.h" void RayCast3D::set_target_position(const Vector3 &p_point) { target_position = p_point; - update_gizmo(); + update_gizmos(); if (Engine::get_singleton()->is_editor_hint()) { if (is_inside_tree()) { @@ -60,20 +58,22 @@ uint32_t RayCast3D::get_collision_mask() const { return collision_mask; } -void RayCast3D::set_collision_mask_bit(int p_bit, bool p_value) { - ERR_FAIL_INDEX_MSG(p_bit, 32, "Collision mask bit must be between 0 and 31 inclusive."); +void RayCast3D::set_collision_mask_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive."); uint32_t mask = get_collision_mask(); if (p_value) { - mask |= 1 << p_bit; + mask |= 1 << (p_layer_number - 1); } else { - mask &= ~(1 << p_bit); + mask &= ~(1 << (p_layer_number - 1)); } set_collision_mask(mask); } -bool RayCast3D::get_collision_mask_bit(int p_bit) const { - ERR_FAIL_INDEX_V_MSG(p_bit, 32, false, "Collision mask bit must be between 0 and 31 inclusive."); - return get_collision_mask() & (1 << p_bit); +bool RayCast3D::get_collision_mask_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Collision layer number must be between 1 and 32 inclusive."); + return get_collision_mask() & (1 << (p_layer_number - 1)); } bool RayCast3D::is_colliding() const { @@ -102,7 +102,7 @@ Vector3 RayCast3D::get_collision_normal() const { void RayCast3D::set_enabled(bool p_enabled) { enabled = p_enabled; - update_gizmo(); + update_gizmos(); if (is_inside_tree() && !Engine::get_singleton()->is_editor_hint()) { set_physics_process_internal(p_enabled); @@ -303,8 +303,8 @@ void RayCast3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &RayCast3D::set_collision_mask); ClassDB::bind_method(D_METHOD("get_collision_mask"), &RayCast3D::get_collision_mask); - ClassDB::bind_method(D_METHOD("set_collision_mask_bit", "bit", "value"), &RayCast3D::set_collision_mask_bit); - ClassDB::bind_method(D_METHOD("get_collision_mask_bit", "bit"), &RayCast3D::get_collision_mask_bit); + ClassDB::bind_method(D_METHOD("set_collision_mask_value", "layer_number", "value"), &RayCast3D::set_collision_mask_value); + ClassDB::bind_method(D_METHOD("get_collision_mask_value", "layer_number"), &RayCast3D::get_collision_mask_value); ClassDB::bind_method(D_METHOD("set_exclude_parent_body", "mask"), &RayCast3D::set_exclude_parent_body); ClassDB::bind_method(D_METHOD("get_exclude_parent_body"), &RayCast3D::get_exclude_parent_body); @@ -366,7 +366,7 @@ void RayCast3D::_update_debug_shape_vertices() { void RayCast3D::set_debug_shape_thickness(const float p_debug_shape_thickness) { debug_shape_thickness = p_debug_shape_thickness; - update_gizmo(); + update_gizmos(); if (Engine::get_singleton()->is_editor_hint()) { if (is_inside_tree()) { diff --git a/scene/3d/ray_cast_3d.h b/scene/3d/ray_cast_3d.h index 968cede9f2..3828bfb4c4 100644 --- a/scene/3d/ray_cast_3d.h +++ b/scene/3d/ray_cast_3d.h @@ -86,8 +86,8 @@ public: void set_collision_mask(uint32_t p_mask); uint32_t get_collision_mask() const; - void set_collision_mask_bit(int p_bit, bool p_value); - bool get_collision_mask_bit(int p_bit) const; + void set_collision_mask_value(int p_layer_number, bool p_value); + bool get_collision_mask_value(int p_layer_number) const; void set_exclude_parent_body(bool p_exclude_parent_body); bool get_exclude_parent_body() const; diff --git a/scene/3d/reflection_probe.cpp b/scene/3d/reflection_probe.cpp index c289fc4fd7..719dbedd94 100644 --- a/scene/3d/reflection_probe.cpp +++ b/scene/3d/reflection_probe.cpp @@ -101,7 +101,7 @@ void ReflectionProbe::set_extents(const Vector3 &p_extents) { RS::get_singleton()->reflection_probe_set_extents(probe, extents); RS::get_singleton()->reflection_probe_set_origin_offset(probe, origin_offset); - update_gizmo(); + update_gizmos(); } Vector3 ReflectionProbe::get_extents() const { @@ -119,7 +119,7 @@ void ReflectionProbe::set_origin_offset(const Vector3 &p_extents) { RS::get_singleton()->reflection_probe_set_extents(probe, extents); RS::get_singleton()->reflection_probe_set_origin_offset(probe, origin_offset); - update_gizmo(); + update_gizmos(); } Vector3 ReflectionProbe::get_origin_offset() const { diff --git a/scene/3d/reflection_probe.h b/scene/3d/reflection_probe.h index 4bf20c89c5..d1b9b12f65 100644 --- a/scene/3d/reflection_probe.h +++ b/scene/3d/reflection_probe.h @@ -32,9 +32,6 @@ #define REFLECTIONPROBE_H #include "scene/3d/visual_instance_3d.h" -#include "scene/resources/sky.h" -#include "scene/resources/texture.h" -#include "servers/rendering_server.h" class ReflectionProbe : public VisualInstance3D { GDCLASS(ReflectionProbe, VisualInstance3D); diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index fa3b16935c..0f5de621ea 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -30,11 +30,10 @@ #include "skeleton_3d.h" -#include "core/config/engine.h" -#include "core/config/project_settings.h" #include "core/object/message_queue.h" #include "core/variant/type_info.h" #include "scene/3d/physics_body_3d.h" +#include "scene/resources/skeleton_modification_3d.h" #include "scene/resources/surface_tool.h" #include "scene/scene_string_names.h" @@ -72,6 +71,13 @@ SkinReference::~SkinReference() { bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) { String path = p_path; +#ifndef _3D_DISABLED + if (path.begins_with("modification_stack")) { + set_modification_stack(p_value); + return true; + } +#endif //_3D_DISABLED + if (!path.begins_with("bones/")) { return false; } @@ -104,6 +110,13 @@ bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) { bool Skeleton3D::_get(const StringName &p_path, Variant &r_ret) const { String path = p_path; +#ifndef _3D_DISABLED + if (path.begins_with("modification_stack")) { + r_ret = modification_stack; + return true; + } +#endif //_3D_DISABLED + if (!path.begins_with("bones/")) { return false; } @@ -139,6 +152,14 @@ void Skeleton3D::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::BOOL, prep + "enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, prep + "pose", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); } + +#ifndef _3D_DISABLED + p_list->push_back( + PropertyInfo(Variant::OBJECT, "modification_stack", + PROPERTY_HINT_RESOURCE_TYPE, + "SkeletonModificationStack3D", + PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DEFERRED_SET_RESOURCE | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE)); +#endif //_3D_DISABLED } void Skeleton3D::_update_process_order() { @@ -149,47 +170,32 @@ void Skeleton3D::_update_process_order() { Bone *bonesptr = bones.ptrw(); int len = bones.size(); - process_order.resize(len); - int *order = process_order.ptrw(); + parentless_bones.clear(); + + for (int i = 0; i < len; i++) { + bonesptr[i].child_bones.clear(); + } + for (int i = 0; i < len; i++) { if (bonesptr[i].parent >= len) { //validate this just in case ERR_PRINT("Bone " + itos(i) + " has invalid parent: " + itos(bonesptr[i].parent)); bonesptr[i].parent = -1; } - order[i] = i; - bonesptr[i].sort_index = i; - } - //now check process order - int pass_count = 0; - while (pass_count < len * len) { - //using bubblesort because of simplicity, it won't run every frame though. - //bublesort worst case is O(n^2), and this may be an infinite loop if cyclic - bool swapped = false; - for (int i = 0; i < len; i++) { - int parent_idx = bonesptr[order[i]].parent; - if (parent_idx < 0) { - continue; //do nothing because it has no parent - } - //swap indices - int parent_order = bonesptr[parent_idx].sort_index; - if (parent_order > i) { - bonesptr[order[i]].sort_index = parent_order; - bonesptr[parent_idx].sort_index = i; - //swap order - SWAP(order[i], order[parent_order]); - swapped = true; - } - } - if (!swapped) { - break; - } - pass_count++; - } + if (bonesptr[i].parent != -1) { + int parent_bone_idx = bonesptr[i].parent; - if (pass_count == len * len) { - ERR_PRINT("Skeleton3D parenthood graph is cyclic"); + // Check to see if this node is already added to the parent: + if (bonesptr[parent_bone_idx].child_bones.find(i) < 0) { + // Add the child node + bonesptr[parent_bone_idx].child_bones.push_back(i); + } else { + ERR_PRINT("Skeleton3D parenthood graph is cyclic"); + } + } else { + parentless_bones.push_back(i); + } } process_order_dirty = false; @@ -200,78 +206,12 @@ void Skeleton3D::_notification(int p_what) { case NOTIFICATION_UPDATE_SKELETON: { RenderingServer *rs = RenderingServer::get_singleton(); Bone *bonesptr = bones.ptrw(); - int len = bones.size(); - _update_process_order(); - - const int *order = process_order.ptr(); - - for (int i = 0; i < len; i++) { - Bone &b = bonesptr[order[i]]; - - if (b.disable_rest) { - if (b.enabled) { - Transform3D pose = b.pose; - if (b.custom_pose_enable) { - pose = b.custom_pose * pose; - } - if (b.parent >= 0) { - b.pose_global = bonesptr[b.parent].pose_global * pose; - b.pose_global_no_override = bonesptr[b.parent].pose_global * pose; - } else { - b.pose_global = pose; - b.pose_global_no_override = pose; - } - } else { - if (b.parent >= 0) { - b.pose_global = bonesptr[b.parent].pose_global; - b.pose_global_no_override = bonesptr[b.parent].pose_global; - } else { - b.pose_global = Transform3D(); - b.pose_global_no_override = Transform3D(); - } - } - - } else { - if (b.enabled) { - Transform3D pose = b.pose; - if (b.custom_pose_enable) { - pose = b.custom_pose * pose; - } - if (b.parent >= 0) { - b.pose_global = bonesptr[b.parent].pose_global * (b.rest * pose); - b.pose_global_no_override = bonesptr[b.parent].pose_global * (b.rest * pose); - } else { - b.pose_global = b.rest * pose; - b.pose_global_no_override = b.rest * pose; - } - } else { - if (b.parent >= 0) { - b.pose_global = bonesptr[b.parent].pose_global * b.rest; - b.pose_global_no_override = bonesptr[b.parent].pose_global * b.rest; - } else { - b.pose_global = b.rest; - b.pose_global_no_override = b.rest; - } - } - } - - if (b.global_pose_override_amount >= CMP_EPSILON) { - b.pose_global = b.pose_global.interpolate_with(b.global_pose_override, b.global_pose_override_amount); - } - - if (b.global_pose_override_reset) { - b.global_pose_override_amount = 0.0; - } + int len = bones.size(); + dirty = false; - for (List<ObjectID>::Element *E = b.nodes_bound.front(); E; E = E->next()) { - Object *obj = ObjectDB::get_instance(E->get()); - ERR_CONTINUE(!obj); - Node3D *node_3d = Object::cast_to<Node3D>(obj); - ERR_CONTINUE(!node_3d); - node_3d->set_transform(b.pose_global); - } - } + // Update bone transforms + force_update_all_bone_transforms(); //update skins for (Set<SkinReference *>::Element *E = skin_bindings.front(); E; E = E->next()) { @@ -329,32 +269,53 @@ void Skeleton3D::_notification(int p_what) { } } - dirty = false; - #ifdef TOOLS_ENABLED emit_signal(SceneStringNames::get_singleton()->pose_updated); #endif // TOOLS_ENABLED } break; +#ifndef _3D_DISABLED 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 (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 (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 (modification_stack.is_valid()) { + execute_modifications(get_physics_process_delta_time(), SkeletonModificationStack3D::EXECUTION_MODE::execution_mode_physics_process); + } + + } break; +#endif // _3D_DISABLED + +#ifndef _3D_DISABLED + case NOTIFICATION_INTERNAL_PROCESS: { + if (modification_stack.is_valid()) { + execute_modifications(get_process_delta_time(), SkeletonModificationStack3D::EXECUTION_MODE::execution_mode_process); + } } break; +#endif // _3D_DISABLED + +#ifndef _3D_DISABLED case NOTIFICATION_READY: { - if (Engine::get_singleton()->is_editor_hint()) { - set_physics_process_internal(true); + set_physics_process_internal(true); + set_process_internal(true); + + if (modification_stack.is_valid()) { + set_modification_stack(modification_stack); } } break; +#endif // _3D_DISABLED } } @@ -366,16 +327,24 @@ void Skeleton3D::clear_bones_global_pose_override() { _make_dirty(); } -void Skeleton3D::set_bone_global_pose_override(int p_bone, const Transform3D &p_pose, float p_amount, bool p_persistent) { - ERR_FAIL_INDEX(p_bone, bones.size()); +void Skeleton3D::set_bone_global_pose_override(int p_bone, const Transform3D &p_pose, real_t p_amount, bool p_persistent) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); bones.write[p_bone].global_pose_override_amount = p_amount; bones.write[p_bone].global_pose_override = p_pose; bones.write[p_bone].global_pose_override_reset = !p_persistent; _make_dirty(); } +Transform3D Skeleton3D::get_bone_global_pose_override(int p_bone) const { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); + return bones[p_bone].global_pose_override; +} + Transform3D Skeleton3D::get_bone_global_pose(int p_bone) const { - ERR_FAIL_INDEX_V(p_bone, bones.size(), Transform3D()); + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); if (dirty) { const_cast<Skeleton3D *>(this)->notification(NOTIFICATION_UPDATE_SKELETON); } @@ -383,13 +352,107 @@ Transform3D Skeleton3D::get_bone_global_pose(int p_bone) const { } Transform3D Skeleton3D::get_bone_global_pose_no_override(int p_bone) const { - ERR_FAIL_INDEX_V(p_bone, bones.size(), Transform3D()); + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); if (dirty) { const_cast<Skeleton3D *>(this)->notification(NOTIFICATION_UPDATE_SKELETON); } return bones[p_bone].pose_global_no_override; } +void Skeleton3D::clear_bones_local_pose_override() { + for (int i = 0; i < bones.size(); i += 1) { + bones.write[i].local_pose_override_amount = 0; + } + _make_dirty(); +} + +void Skeleton3D::set_bone_local_pose_override(int p_bone, const Transform3D &p_pose, real_t p_amount, bool p_persistent) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); + bones.write[p_bone].local_pose_override_amount = p_amount; + bones.write[p_bone].local_pose_override = p_pose; + bones.write[p_bone].local_pose_override_reset = !p_persistent; + _make_dirty(); +} + +Transform3D Skeleton3D::get_bone_local_pose_override(int p_bone) const { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); + return bones[p_bone].local_pose_override; +} + +void Skeleton3D::update_bone_rest_forward_vector(int p_bone, bool p_force_update) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); + + if (bones[p_bone].rest_bone_forward_vector.length_squared() > 0 && p_force_update == false) { + update_bone_rest_forward_axis(p_bone, p_force_update); + } + + // If it is a child/leaf bone... + if (get_bone_parent(p_bone) > 0) { + bones.write[p_bone].rest_bone_forward_vector = bones[p_bone].rest.origin.normalized(); + } else { + // If it has children... + Vector<int> child_bones = get_bone_children(p_bone); + if (child_bones.size() > 0) { + Vector3 combined_child_dir = Vector3(0, 0, 0); + for (int i = 0; i < child_bones.size(); i++) { + combined_child_dir += bones[child_bones[i]].rest.origin.normalized(); + } + combined_child_dir = combined_child_dir / child_bones.size(); + bones.write[p_bone].rest_bone_forward_vector = combined_child_dir.normalized(); + } else { + WARN_PRINT_ONCE("Cannot calculate forward direction for bone " + itos(p_bone)); + WARN_PRINT_ONCE("Assuming direction of (0, 1, 0) for bone"); + bones.write[p_bone].rest_bone_forward_vector = Vector3(0, 1, 0); + } + } + update_bone_rest_forward_axis(p_bone, p_force_update); +} + +void Skeleton3D::update_bone_rest_forward_axis(int p_bone, bool p_force_update) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); + if (bones[p_bone].rest_bone_forward_axis > -1 && p_force_update == false) { + return; + } + + Vector3 forward_axis_absolute = bones[p_bone].rest_bone_forward_vector.abs(); + if (forward_axis_absolute.x > forward_axis_absolute.y && forward_axis_absolute.x > forward_axis_absolute.z) { + if (bones[p_bone].rest_bone_forward_vector.x > 0) { + bones.write[p_bone].rest_bone_forward_axis = BONE_AXIS_X_FORWARD; + } else { + bones.write[p_bone].rest_bone_forward_axis = BONE_AXIS_NEGATIVE_X_FORWARD; + } + } else if (forward_axis_absolute.y > forward_axis_absolute.x && forward_axis_absolute.y > forward_axis_absolute.z) { + if (bones[p_bone].rest_bone_forward_vector.y > 0) { + bones.write[p_bone].rest_bone_forward_axis = BONE_AXIS_Y_FORWARD; + } else { + bones.write[p_bone].rest_bone_forward_axis = BONE_AXIS_NEGATIVE_Y_FORWARD; + } + } else { + if (bones[p_bone].rest_bone_forward_vector.z > 0) { + bones.write[p_bone].rest_bone_forward_axis = BONE_AXIS_Z_FORWARD; + } else { + bones.write[p_bone].rest_bone_forward_axis = BONE_AXIS_NEGATIVE_Z_FORWARD; + } + } +} + +Vector3 Skeleton3D::get_bone_axis_forward_vector(int p_bone) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, Vector3(0, 0, 0)); + return bones[p_bone].rest_bone_forward_vector; +} + +int Skeleton3D::get_bone_axis_forward_enum(int p_bone) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, -1); + return bones[p_bone].rest_bone_forward_axis; +} + // skeleton creation api void Skeleton3D::add_bone(const String &p_name) { ERR_FAIL_COND(p_name == "" || p_name.find(":") != -1 || p_name.find("/") != -1); @@ -404,7 +467,7 @@ void Skeleton3D::add_bone(const String &p_name) { process_order_dirty = true; version++; _make_dirty(); - update_gizmo(); + update_gizmos(); } int Skeleton3D::find_bone(const String &p_name) const { @@ -418,14 +481,15 @@ int Skeleton3D::find_bone(const String &p_name) const { } String Skeleton3D::get_bone_name(int p_bone) const { - ERR_FAIL_INDEX_V(p_bone, bones.size(), ""); - + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, ""); return bones[p_bone].name; } void Skeleton3D::set_bone_name(int p_bone, const String &p_name) { - ERR_FAIL_INDEX(p_bone, bones.size()); + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); - for (int i = 0; i < bones.size(); i++) { + for (int i = 0; i < bone_size; i++) { if (i != p_bone) { ERR_FAIL_COND(bones[i].name == p_name); } @@ -453,7 +517,8 @@ int Skeleton3D::get_bone_count() const { } void Skeleton3D::set_bone_parent(int p_bone, int p_parent) { - ERR_FAIL_INDEX(p_bone, bones.size()); + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); ERR_FAIL_COND(p_parent != -1 && (p_parent < 0)); bones.write[p_bone].parent = p_parent; @@ -462,7 +527,8 @@ void Skeleton3D::set_bone_parent(int p_bone, int p_parent) { } void Skeleton3D::unparent_bone_and_rest(int p_bone) { - ERR_FAIL_INDEX(p_bone, bones.size()); + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); _update_process_order(); @@ -479,76 +545,93 @@ void Skeleton3D::unparent_bone_and_rest(int p_bone) { } void Skeleton3D::set_bone_disable_rest(int p_bone, bool p_disable) { - ERR_FAIL_INDEX(p_bone, bones.size()); + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); bones.write[p_bone].disable_rest = p_disable; } bool Skeleton3D::is_bone_rest_disabled(int p_bone) const { - ERR_FAIL_INDEX_V(p_bone, bones.size(), false); + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, false); return bones[p_bone].disable_rest; } int Skeleton3D::get_bone_parent(int p_bone) const { - ERR_FAIL_INDEX_V(p_bone, bones.size(), -1); + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, -1); return bones[p_bone].parent; } -void Skeleton3D::set_bone_rest(int p_bone, const Transform3D &p_rest) { - ERR_FAIL_INDEX(p_bone, bones.size()); +Vector<int> Skeleton3D::get_bone_children(int p_bone) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, Vector<int>()); + return bones[p_bone].child_bones; +} - bones.write[p_bone].rest = p_rest; +void Skeleton3D::set_bone_children(int p_bone, Vector<int> p_children) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); + bones.write[p_bone].child_bones = p_children; + + process_order_dirty = true; _make_dirty(); } -Transform3D Skeleton3D::get_bone_rest(int p_bone) const { - ERR_FAIL_INDEX_V(p_bone, bones.size(), Transform3D()); - return bones[p_bone].rest; +void Skeleton3D::add_bone_child(int p_bone, int p_child) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); + bones.write[p_bone].child_bones.push_back(p_child); + + process_order_dirty = true; + _make_dirty(); } -void Skeleton3D::set_bone_enabled(int p_bone, bool p_enabled) { - ERR_FAIL_INDEX(p_bone, bones.size()); +void Skeleton3D::remove_bone_child(int p_bone, int p_child) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); - bones.write[p_bone].enabled = p_enabled; + int child_idx = bones[p_bone].child_bones.find(p_child); + if (child_idx >= 0) { + bones.write[p_bone].child_bones.remove(child_idx); + } else { + WARN_PRINT("Cannot remove child bone: Child bone not found."); + } + + process_order_dirty = true; _make_dirty(); } -bool Skeleton3D::is_bone_enabled(int p_bone) const { - ERR_FAIL_INDEX_V(p_bone, bones.size(), false); - return bones[p_bone].enabled; +Vector<int> Skeleton3D::get_parentless_bones() { + return parentless_bones; } -void Skeleton3D::bind_child_node_to_bone(int p_bone, Node *p_node) { - ERR_FAIL_NULL(p_node); - ERR_FAIL_INDEX(p_bone, bones.size()); - - ObjectID id = p_node->get_instance_id(); +void Skeleton3D::set_bone_rest(int p_bone, const Transform3D &p_rest) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); - for (const List<ObjectID>::Element *E = bones[p_bone].nodes_bound.front(); E; E = E->next()) { - if (E->get() == id) { - return; // already here - } - } + bones.write[p_bone].rest = p_rest; + _make_dirty(); +} +Transform3D Skeleton3D::get_bone_rest(int p_bone) const { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); - bones.write[p_bone].nodes_bound.push_back(id); + return bones[p_bone].rest; } -void Skeleton3D::unbind_child_node_from_bone(int p_bone, Node *p_node) { - ERR_FAIL_NULL(p_node); - ERR_FAIL_INDEX(p_bone, bones.size()); +void Skeleton3D::set_bone_enabled(int p_bone, bool p_enabled) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); - ObjectID id = p_node->get_instance_id(); - bones.write[p_bone].nodes_bound.erase(id); + bones.write[p_bone].enabled = p_enabled; + _make_dirty(); } -void Skeleton3D::get_bound_child_nodes_to_bone(int p_bone, List<Node *> *p_bound) const { - ERR_FAIL_INDEX(p_bone, bones.size()); - - for (const List<ObjectID>::Element *E = bones[p_bone].nodes_bound.front(); E; E = E->next()) { - Object *obj = ObjectDB::get_instance(E->get()); - ERR_CONTINUE(!obj); - p_bound->push_back(Object::cast_to<Node>(obj)); - } +bool Skeleton3D::is_bone_enabled(int p_bone) const { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, false); + return bones[p_bone].enabled; } void Skeleton3D::clear_bones() { @@ -561,7 +644,8 @@ void Skeleton3D::clear_bones() { // posing api void Skeleton3D::set_bone_pose(int p_bone, const Transform3D &p_pose) { - ERR_FAIL_INDEX(p_bone, bones.size()); + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); bones.write[p_bone].pose = p_pose; if (is_inside_tree()) { @@ -569,12 +653,14 @@ void Skeleton3D::set_bone_pose(int p_bone, const Transform3D &p_pose) { } } Transform3D Skeleton3D::get_bone_pose(int p_bone) const { - ERR_FAIL_INDEX_V(p_bone, bones.size(), Transform3D()); + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); return bones[p_bone].pose; } void Skeleton3D::set_bone_custom_pose(int p_bone, const Transform3D &p_custom_pose) { - ERR_FAIL_INDEX(p_bone, bones.size()); + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); //ERR_FAIL_COND( !is_inside_scene() ); bones.write[p_bone].custom_pose_enable = (p_custom_pose != Transform3D()); @@ -584,7 +670,8 @@ void Skeleton3D::set_bone_custom_pose(int p_bone, const Transform3D &p_custom_po } Transform3D Skeleton3D::get_bone_custom_pose(int p_bone) const { - ERR_FAIL_INDEX_V(p_bone, bones.size(), Transform3D()); + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); return bones[p_bone].custom_pose; } @@ -597,24 +684,22 @@ void Skeleton3D::_make_dirty() { dirty = true; } -int Skeleton3D::get_process_order(int p_idx) { - ERR_FAIL_INDEX_V(p_idx, bones.size(), -1); +void Skeleton3D::localize_rests() { _update_process_order(); - return process_order[p_idx]; -} -Vector<int> Skeleton3D::get_bone_process_orders() { - _update_process_order(); - return process_order; -} + Vector<int> bones_to_process = get_parentless_bones(); + while (bones_to_process.size() > 0) { + int current_bone_idx = bones_to_process[0]; + bones_to_process.erase(current_bone_idx); -void Skeleton3D::localize_rests() { - _update_process_order(); + if (bones[current_bone_idx].parent >= 0) { + set_bone_rest(current_bone_idx, bones[bones[current_bone_idx].parent].rest.affine_inverse() * bones[current_bone_idx].rest); + } - for (int i = bones.size() - 1; i >= 0; i--) { - int idx = process_order[i]; - if (bones[idx].parent >= 0) { - set_bone_rest(idx, bones[bones[idx].parent].rest.affine_inverse() * bones[idx].rest); + // Add the bone's children to the list of bones to be processed + int child_bone_size = bones[current_bone_idx].child_bones.size(); + for (int i = 0; i < child_bone_size; i++) { + bones_to_process.push_back(bones[current_bone_idx].child_bones[i]); } } } @@ -641,7 +726,8 @@ bool Skeleton3D::get_animate_physical_bones() const { } void Skeleton3D::bind_physical_bone_to_bone(int p_bone, PhysicalBone3D *p_physical_bone) { - ERR_FAIL_INDEX(p_bone, bones.size()); + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); ERR_FAIL_COND(bones[p_bone].physical_bone); ERR_FAIL_COND(!p_physical_bone); bones.write[p_bone].physical_bone = p_physical_bone; @@ -650,20 +736,23 @@ void Skeleton3D::bind_physical_bone_to_bone(int p_bone, PhysicalBone3D *p_physic } void Skeleton3D::unbind_physical_bone_from_bone(int p_bone) { - ERR_FAIL_INDEX(p_bone, bones.size()); + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); bones.write[p_bone].physical_bone = nullptr; _rebuild_physical_bones_cache(); } PhysicalBone3D *Skeleton3D::get_physical_bone(int p_bone) { - ERR_FAIL_INDEX_V(p_bone, bones.size(), nullptr); + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, nullptr); return bones[p_bone].physical_bone; } PhysicalBone3D *Skeleton3D::get_physical_bone_parent(int p_bone) { - ERR_FAIL_INDEX_V(p_bone, bones.size(), nullptr); + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, nullptr); if (bones[p_bone].cache_parent_physical_bone) { return bones[p_bone].cache_parent_physical_bone; @@ -673,7 +762,8 @@ PhysicalBone3D *Skeleton3D::get_physical_bone_parent(int p_bone) { } PhysicalBone3D *Skeleton3D::_get_physical_bone_parent(int p_bone) { - ERR_FAIL_INDEX_V(p_bone, bones.size(), nullptr); + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, nullptr); const int parent_bone = bones[p_bone].parent; if (0 > parent_bone) { @@ -804,15 +894,26 @@ Ref<SkinReference> Skeleton3D::register_skin(const Ref<Skin> &p_skin) { // pose changed, rebuild cache of inverses const Bone *bonesptr = bones.ptr(); int len = bones.size(); - const int *order = process_order.ptr(); // calculate global rests and invert them - for (int i = 0; i < len; i++) { - const Bone &b = bonesptr[order[i]]; - if (b.parent >= 0) { - skin->set_bind_pose(order[i], skin->get_bind_pose(b.parent) * b.rest); - } else { - skin->set_bind_pose(order[i], b.rest); + LocalVector<int> bones_to_process; + bones_to_process = get_parentless_bones(); + while (bones_to_process.size() > 0) { + int current_bone_idx = bones_to_process[0]; + const Bone &b = bonesptr[current_bone_idx]; + bones_to_process.erase(current_bone_idx); + LocalVector<int> child_bones_vector; + child_bones_vector = get_bone_children(current_bone_idx); + int child_bones_size = child_bones_vector.size(); + if (b.parent < 0) { + skin->set_bind_pose(current_bone_idx, b.rest); + } + for (int i = 0; i < child_bones_size; i++) { + int child_bone_idx = child_bones_vector[i]; + const Bone &cb = bonesptr[child_bone_idx]; + skin->set_bind_pose(child_bone_idx, skin->get_bind_pose(current_bone_idx) * cb.rest); + // Add the bone's children to the list of bones to be processed. + bones_to_process.push_back(child_bones_vector[i]); } } @@ -843,17 +944,202 @@ Ref<SkinReference> Skeleton3D::register_skin(const Ref<Skin> &p_skin) { return skin_ref; } +void Skeleton3D::force_update_all_bone_transforms() { + _update_process_order(); + + for (int i = 0; i < parentless_bones.size(); i++) { + force_update_bone_children_transforms(parentless_bones[i]); + } +} + +void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone_idx, bone_size); + + Bone *bonesptr = bones.ptrw(); + List<int> bones_to_process = List<int>(); + bones_to_process.push_back(p_bone_idx); + + while (bones_to_process.size() > 0) { + int current_bone_idx = bones_to_process[0]; + bones_to_process.erase(current_bone_idx); + + Bone &b = bonesptr[current_bone_idx]; + + if (b.disable_rest) { + if (b.enabled) { + Transform3D pose = b.pose; + if (b.custom_pose_enable) { + pose = b.custom_pose * pose; + } + if (b.parent >= 0) { + b.pose_global = bonesptr[b.parent].pose_global * pose; + b.pose_global_no_override = b.pose_global; + } else { + b.pose_global = pose; + b.pose_global_no_override = b.pose_global; + } + } else { + if (b.parent >= 0) { + b.pose_global = bonesptr[b.parent].pose_global; + b.pose_global_no_override = b.pose_global; + } else { + b.pose_global = Transform3D(); + b.pose_global_no_override = b.pose_global; + } + } + + } else { + if (b.enabled) { + Transform3D pose = b.pose; + if (b.custom_pose_enable) { + pose = b.custom_pose * pose; + } + if (b.parent >= 0) { + b.pose_global = bonesptr[b.parent].pose_global * (b.rest * pose); + b.pose_global_no_override = b.pose_global; + } else { + b.pose_global = b.rest * pose; + b.pose_global_no_override = b.pose_global; + } + } else { + if (b.parent >= 0) { + b.pose_global = bonesptr[b.parent].pose_global * b.rest; + b.pose_global_no_override = b.pose_global; + } else { + b.pose_global = b.rest; + b.pose_global_no_override = b.pose_global; + } + } + } + + if (b.local_pose_override_amount >= CMP_EPSILON) { + Transform3D override_local_pose; + if (b.parent >= 0) { + override_local_pose = bonesptr[b.parent].pose_global * (b.rest * b.local_pose_override); + } else { + override_local_pose = (b.rest * b.local_pose_override); + } + b.pose_global = b.pose_global.interpolate_with(override_local_pose, b.local_pose_override_amount); + } + + if (b.global_pose_override_amount >= CMP_EPSILON) { + b.pose_global = b.pose_global.interpolate_with(b.global_pose_override, b.global_pose_override_amount); + } + + if (b.local_pose_override_reset) { + b.local_pose_override_amount = 0.0; + } + if (b.global_pose_override_reset) { + b.global_pose_override_amount = 0.0; + } + + // Add the bone's children to the list of bones to be processed + int child_bone_size = b.child_bones.size(); + for (int i = 0; i < child_bone_size; i++) { + bones_to_process.push_back(b.child_bones[i]); + } + + emit_signal(SceneStringNames::get_singleton()->bone_pose_changed, current_bone_idx); + } +} + // helper functions -Transform3D Skeleton3D::bone_transform_to_world_transform(Transform3D p_bone_transform) { - return get_global_transform() * p_bone_transform; + +Transform3D Skeleton3D::global_pose_to_world_transform(Transform3D p_global_pose) { + return get_global_transform() * p_global_pose; } -Transform3D Skeleton3D::world_transform_to_bone_transform(Transform3D p_world_transform) { +Transform3D Skeleton3D::world_transform_to_global_pose(Transform3D p_world_transform) { return get_global_transform().affine_inverse() * p_world_transform; } +Transform3D Skeleton3D::global_pose_to_local_pose(int p_bone_idx, Transform3D p_global_pose) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone_idx, bone_size, Transform3D()); + if (bones[p_bone_idx].parent >= 0) { + int parent_bone_idx = bones[p_bone_idx].parent; + Transform3D conversion_transform = (bones[parent_bone_idx].pose_global * bones[p_bone_idx].rest); + return conversion_transform.affine_inverse() * p_global_pose; + } else { + return p_global_pose; + } +} + +Transform3D Skeleton3D::local_pose_to_global_pose(int p_bone_idx, Transform3D p_local_pose) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone_idx, bone_size, Transform3D()); + if (bones[p_bone_idx].parent >= 0) { + int parent_bone_idx = bones[p_bone_idx].parent; + Transform3D conversion_transform = (bones[parent_bone_idx].pose_global * bones[p_bone_idx].rest); + return conversion_transform * p_local_pose; + } else { + return p_local_pose; + } +} + +Basis Skeleton3D::global_pose_z_forward_to_bone_forward(int p_bone_idx, Basis p_basis) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone_idx, bone_size, Basis()); + Basis return_basis = p_basis; + + if (bones[p_bone_idx].rest_bone_forward_axis < 0) { + update_bone_rest_forward_vector(p_bone_idx, true); + } + + if (bones[p_bone_idx].rest_bone_forward_axis == BONE_AXIS_X_FORWARD) { + return_basis.rotate_local(Vector3(0, 1, 0), (Math_PI / 2.0)); + } else if (bones[p_bone_idx].rest_bone_forward_axis == BONE_AXIS_NEGATIVE_X_FORWARD) { + return_basis.rotate_local(Vector3(0, 1, 0), -(Math_PI / 2.0)); + } else if (bones[p_bone_idx].rest_bone_forward_axis == BONE_AXIS_Y_FORWARD) { + return_basis.rotate_local(Vector3(1, 0, 0), -(Math_PI / 2.0)); + } else if (bones[p_bone_idx].rest_bone_forward_axis == BONE_AXIS_NEGATIVE_Y_FORWARD) { + return_basis.rotate_local(Vector3(1, 0, 0), (Math_PI / 2.0)); + } else if (bones[p_bone_idx].rest_bone_forward_axis == BONE_AXIS_Z_FORWARD) { + // Do nothing! + } else if (bones[p_bone_idx].rest_bone_forward_axis == BONE_AXIS_NEGATIVE_Z_FORWARD) { + return_basis.rotate_local(Vector3(0, 0, 1), Math_PI); + } + + return return_basis; +} + +// Modifications + +#ifndef _3D_DISABLED + +void Skeleton3D::set_modification_stack(Ref<SkeletonModificationStack3D> p_stack) { + if (modification_stack.is_valid()) { + modification_stack->is_setup = false; + modification_stack->set_skeleton(nullptr); + } + + modification_stack = p_stack; + if (modification_stack.is_valid()) { + modification_stack->set_skeleton(this); + modification_stack->setup(); + } +} +Ref<SkeletonModificationStack3D> Skeleton3D::get_modification_stack() { + return modification_stack; +} + +void Skeleton3D::execute_modifications(real_t p_delta, int p_execution_mode) { + if (!modification_stack.is_valid()) { + return; + } + + // Needed to avoid the issue where the stack looses reference to the skeleton when the scene is saved. + if (modification_stack->skeleton != this) { + modification_stack->set_skeleton(this); + } + + modification_stack->execute(p_delta, p_execution_mode); +} + +#endif // _3D_DISABLED + void Skeleton3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_bone_process_orders"), &Skeleton3D::get_bone_process_orders); ClassDB::bind_method(D_METHOD("add_bone", "name"), &Skeleton3D::add_bone); ClassDB::bind_method(D_METHOD("find_bone", "name"), &Skeleton3D::find_bone); ClassDB::bind_method(D_METHOD("get_bone_name", "bone_idx"), &Skeleton3D::get_bone_name); @@ -866,6 +1152,13 @@ void Skeleton3D::_bind_methods() { ClassDB::bind_method(D_METHOD("unparent_bone_and_rest", "bone_idx"), &Skeleton3D::unparent_bone_and_rest); + ClassDB::bind_method(D_METHOD("get_bone_children", "bone_idx"), &Skeleton3D::get_bone_children); + ClassDB::bind_method(D_METHOD("set_bone_children", "bone_idx", "bone_children"), &Skeleton3D::set_bone_children); + ClassDB::bind_method(D_METHOD("add_bone_child", "bone_idx", "child_bone_idx"), &Skeleton3D::add_bone_child); + ClassDB::bind_method(D_METHOD("remove_bone_child", "bone_idx", "child_bone_idx"), &Skeleton3D::remove_bone_child); + + ClassDB::bind_method(D_METHOD("get_parentless_bones"), &Skeleton3D::get_parentless_bones); + 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); @@ -883,14 +1176,26 @@ void Skeleton3D::_bind_methods() { ClassDB::bind_method(D_METHOD("clear_bones_global_pose_override"), &Skeleton3D::clear_bones_global_pose_override); ClassDB::bind_method(D_METHOD("set_bone_global_pose_override", "bone_idx", "pose", "amount", "persistent"), &Skeleton3D::set_bone_global_pose_override, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_bone_global_pose_override", "bone_idx"), &Skeleton3D::get_bone_global_pose_override); ClassDB::bind_method(D_METHOD("get_bone_global_pose", "bone_idx"), &Skeleton3D::get_bone_global_pose); ClassDB::bind_method(D_METHOD("get_bone_global_pose_no_override", "bone_idx"), &Skeleton3D::get_bone_global_pose_no_override); + ClassDB::bind_method(D_METHOD("clear_bones_local_pose_override"), &Skeleton3D::clear_bones_local_pose_override); + ClassDB::bind_method(D_METHOD("set_bone_local_pose_override", "bone_idx", "pose", "amount", "persistent"), &Skeleton3D::set_bone_local_pose_override, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_bone_local_pose_override", "bone_idx"), &Skeleton3D::get_bone_local_pose_override); + ClassDB::bind_method(D_METHOD("get_bone_custom_pose", "bone_idx"), &Skeleton3D::get_bone_custom_pose); ClassDB::bind_method(D_METHOD("set_bone_custom_pose", "bone_idx", "custom_pose"), &Skeleton3D::set_bone_custom_pose); - ClassDB::bind_method(D_METHOD("bone_transform_to_world_transform", "bone_transform"), &Skeleton3D::bone_transform_to_world_transform); - ClassDB::bind_method(D_METHOD("world_transform_to_bone_transform", "world_transform"), &Skeleton3D::world_transform_to_bone_transform); + ClassDB::bind_method(D_METHOD("force_update_all_bone_transforms"), &Skeleton3D::force_update_all_bone_transforms); + ClassDB::bind_method(D_METHOD("force_update_bone_child_transform", "bone_idx"), &Skeleton3D::force_update_bone_children_transforms); + + // Helper functions + ClassDB::bind_method(D_METHOD("global_pose_to_world_transform", "global_pose"), &Skeleton3D::global_pose_to_world_transform); + ClassDB::bind_method(D_METHOD("world_transform_to_global_pose", "world_transform"), &Skeleton3D::world_transform_to_global_pose); + ClassDB::bind_method(D_METHOD("global_pose_to_local_pose", "bone_idx", "global_pose"), &Skeleton3D::global_pose_to_local_pose); + ClassDB::bind_method(D_METHOD("local_pose_to_global_pose", "bone_idx", "local_pose"), &Skeleton3D::local_pose_to_global_pose); + ClassDB::bind_method(D_METHOD("global_pose_z_forward_to_bone_forward", "bone_idx", "basis"), &Skeleton3D::global_pose_z_forward_to_bone_forward); ClassDB::bind_method(D_METHOD("set_animate_physical_bones"), &Skeleton3D::set_animate_physical_bones); ClassDB::bind_method(D_METHOD("get_animate_physical_bones"), &Skeleton3D::get_animate_physical_bones); @@ -900,12 +1205,21 @@ void Skeleton3D::_bind_methods() { ClassDB::bind_method(D_METHOD("physical_bones_add_collision_exception", "exception"), &Skeleton3D::physical_bones_add_collision_exception); ClassDB::bind_method(D_METHOD("physical_bones_remove_collision_exception", "exception"), &Skeleton3D::physical_bones_remove_collision_exception); + // Modifications + ClassDB::bind_method(D_METHOD("set_modification_stack", "modification_stack"), &Skeleton3D::set_modification_stack); + ClassDB::bind_method(D_METHOD("get_modification_stack"), &Skeleton3D::get_modification_stack); + ClassDB::bind_method(D_METHOD("execute_modifications", "delta", "execution_mode"), &Skeleton3D::execute_modifications); + +#ifndef _3D_DISABLED ADD_PROPERTY(PropertyInfo(Variant::BOOL, "animate_physical_bones"), "set_animate_physical_bones", "get_animate_physical_bones"); +#endif // _3D_DISABLED #ifdef TOOLS_ENABLED ADD_SIGNAL(MethodInfo("pose_updated")); #endif // TOOLS_ENABLED + ADD_SIGNAL(MethodInfo("bone_pose_changed", PropertyInfo(Variant::INT, "bone_idx"))); + BIND_CONSTANT(NOTIFICATION_UPDATE_SKELETON); } diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h index 2f6e416c8c..c8a19db813 100644 --- a/scene/3d/skeleton_3d.h +++ b/scene/3d/skeleton_3d.h @@ -31,8 +31,8 @@ #ifndef SKELETON_3D_H #define SKELETON_3D_H -#include "core/templates/rid.h" #include "scene/3d/node_3d.h" +#include "scene/resources/skeleton_modification_3d.h" #include "scene/resources/skin.h" typedef int BoneId; @@ -62,6 +62,8 @@ public: ~SkinReference(); }; +class SkeletonModificationStack3D; + class Skeleton3D : public Node3D { GDCLASS(Skeleton3D, Node3D); @@ -71,9 +73,8 @@ private: struct Bone { String name; - bool enabled = true; - int parent = -1; - int sort_index = 0; //used for re-sorting process order + bool enabled; + int parent; bool disable_rest = false; Transform3D rest; @@ -85,14 +86,42 @@ private: bool custom_pose_enable = false; Transform3D custom_pose; - float global_pose_override_amount = 0.0; + real_t global_pose_override_amount = 0.0; bool global_pose_override_reset = false; Transform3D global_pose_override; PhysicalBone3D *physical_bone = nullptr; PhysicalBone3D *cache_parent_physical_bone = nullptr; - List<ObjectID> nodes_bound; + real_t local_pose_override_amount; + bool local_pose_override_reset; + Transform3D local_pose_override; + + Vector<int> child_bones; + + // The forward direction vector and rest bone forward axis are cached because they do not change + // 99% of the time, but recalculating them can be expensive on models with many bones. + Vector3 rest_bone_forward_vector; + int rest_bone_forward_axis = -1; + + Bone() { + parent = -1; + enabled = true; + disable_rest = false; + custom_pose_enable = false; + global_pose_override_amount = 0; + global_pose_override_reset = false; +#ifndef _3D_DISABLED + physical_bone = nullptr; + cache_parent_physical_bone = nullptr; +#endif // _3D_DISABLED + local_pose_override_amount = 0; + local_pose_override_reset = false; + child_bones = Vector<int>(); + + rest_bone_forward_vector = Vector3(0, 0, 0); + rest_bone_forward_axis = -1; + } }; Set<SkinReference *> skin_bindings; @@ -101,8 +130,9 @@ private: bool animate_physical_bones = true; Vector<Bone> bones; - Vector<int> process_order; - bool process_order_dirty = true; + bool process_order_dirty; + + Vector<int> parentless_bones; void _make_dirty(); bool dirty = false; @@ -118,7 +148,20 @@ protected: void _notification(int p_what); static void _bind_methods(); +#ifndef _3D_DISABLED + Ref<SkeletonModificationStack3D> modification_stack; +#endif // _3D_DISABLED + public: + enum Bone_Forward_Axis { + BONE_AXIS_X_FORWARD = 0, + BONE_AXIS_Y_FORWARD = 1, + BONE_AXIS_Z_FORWARD = 2, + BONE_AXIS_NEGATIVE_X_FORWARD = 3, + BONE_AXIS_NEGATIVE_Y_FORWARD = 4, + BONE_AXIS_NEGATIVE_Z_FORWARD = 5, + }; + enum { NOTIFICATION_UPDATE_SKELETON = 50 }; @@ -136,6 +179,12 @@ public: void unparent_bone_and_rest(int p_bone); + Vector<int> get_bone_children(int p_bone); + void set_bone_children(int p_bone, Vector<int> p_children); + void add_bone_child(int p_bone, int p_child); + void remove_bone_child(int p_bone, int p_child); + Vector<int> get_parentless_bones(); + void set_bone_disable_rest(int p_bone, bool p_disable); bool is_bone_rest_disabled(int p_bone) const; @@ -146,16 +195,8 @@ public: Transform3D get_bone_global_pose(int p_bone) const; Transform3D get_bone_global_pose_no_override(int p_bone) const; - void clear_bones_global_pose_override(); - void set_bone_global_pose_override(int p_bone, const Transform3D &p_pose, float p_amount, bool p_persistent = false); - void set_bone_enabled(int p_bone, bool p_enabled); bool is_bone_enabled(int p_bone) const; - - void bind_child_node_to_bone(int p_bone, Node *p_node); - void unbind_child_node_from_bone(int p_bone, Node *p_node); - void get_bound_child_nodes_to_bone(int p_bone, List<Node *> *p_bound) const; - void clear_bones(); // posing api @@ -166,15 +207,40 @@ public: void set_bone_custom_pose(int p_bone, const Transform3D &p_custom_pose); Transform3D get_bone_custom_pose(int p_bone) const; + void clear_bones_global_pose_override(); + Transform3D get_bone_global_pose_override(int p_bone) const; + void set_bone_global_pose_override(int p_bone, const Transform3D &p_pose, real_t p_amount, bool p_persistent = false); + + void clear_bones_local_pose_override(); + Transform3D get_bone_local_pose_override(int p_bone) const; + void set_bone_local_pose_override(int p_bone, const Transform3D &p_pose, real_t p_amount, bool p_persistent = false); + void localize_rests(); // used for loaders and tools - int get_process_order(int p_idx); - Vector<int> get_bone_process_orders(); Ref<SkinReference> register_skin(const Ref<Skin> &p_skin); + void force_update_all_bone_transforms(); + void force_update_bone_children_transforms(int bone_idx); + + void update_bone_rest_forward_vector(int p_bone, bool p_force_update = false); + void update_bone_rest_forward_axis(int p_bone, bool p_force_update = false); + Vector3 get_bone_axis_forward_vector(int p_bone); + int get_bone_axis_forward_enum(int p_bone); + // Helper functions - Transform3D bone_transform_to_world_transform(Transform3D p_transform); - Transform3D world_transform_to_bone_transform(Transform3D p_transform); + Transform3D global_pose_to_world_transform(Transform3D p_global_pose); + Transform3D world_transform_to_global_pose(Transform3D p_transform); + Transform3D global_pose_to_local_pose(int p_bone_idx, Transform3D p_global_pose); + Transform3D local_pose_to_global_pose(int p_bone_idx, Transform3D p_local_pose); + + Basis global_pose_z_forward_to_bone_forward(int p_bone_idx, Basis p_basis); + + // Modifications +#ifndef _3D_DISABLED + Ref<SkeletonModificationStack3D> get_modification_stack(); + void set_modification_stack(Ref<SkeletonModificationStack3D> p_stack); + void execute_modifications(real_t p_delta, int p_execution_mode); +#endif // _3D_DISABLED // Physical bone API diff --git a/scene/3d/skeleton_ik_3d.cpp b/scene/3d/skeleton_ik_3d.cpp index 1005d51e63..466f67afb8 100644 --- a/scene/3d/skeleton_ik_3d.cpp +++ b/scene/3d/skeleton_ik_3d.cpp @@ -85,7 +85,7 @@ bool FabrikInverseKinematic::build_chain(Task *p_task, bool p_force_simple_chain chain_sub_tip = p_task->skeleton->get_bone_parent(chain_sub_tip); } - BoneId middle_chain_item_id = (((float)sub_chain_size) * 0.5); + BoneId middle_chain_item_id = (BoneId)(sub_chain_size * 0.5); // Build chain by reading chain ids in reverse order // For each chain item id will be created a ChainItem if doesn't exists @@ -542,7 +542,7 @@ Transform3D SkeletonIK3D::_get_target_transform() { target_node_override = Object::cast_to<Node3D>(get_node(target_node_path_override)); } - if (target_node_override) { + if (target_node_override && target_node_override->is_inside_tree()) { return target_node_override->get_global_transform(); } else { return target; diff --git a/scene/3d/skeleton_ik_3d.h b/scene/3d/skeleton_ik_3d.h index 81dfe675c3..4cf08e7c99 100644 --- a/scene/3d/skeleton_ik_3d.h +++ b/scene/3d/skeleton_ik_3d.h @@ -33,11 +33,6 @@ #ifndef _3D_DISABLED -/** - * @author AndreaCatania - */ - -#include "core/math/transform_3d.h" #include "scene/3d/skeleton_3d.h" class FabrikInverseKinematic { diff --git a/scene/3d/soft_body_3d.cpp b/scene/3d/soft_body_3d.cpp index efd8a2b50c..7eb189e890 100644 --- a/scene/3d/soft_body_3d.cpp +++ b/scene/3d/soft_body_3d.cpp @@ -30,13 +30,7 @@ #include "soft_body_3d.h" -#include "core/object/class_db.h" -#include "core/os/os.h" -#include "core/templates/list.h" -#include "core/templates/rid.h" -#include "scene/3d/collision_object_3d.h" #include "scene/3d/physics_body_3d.h" -#include "scene/3d/skeleton_3d.h" SoftBodyRenderingServerHandler::SoftBodyRenderingServerHandler() {} @@ -327,11 +321,11 @@ void SoftBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_collision_layer", "collision_layer"), &SoftBody3D::set_collision_layer); ClassDB::bind_method(D_METHOD("get_collision_layer"), &SoftBody3D::get_collision_layer); - ClassDB::bind_method(D_METHOD("set_collision_mask_bit", "bit", "value"), &SoftBody3D::set_collision_mask_bit); - ClassDB::bind_method(D_METHOD("get_collision_mask_bit", "bit"), &SoftBody3D::get_collision_mask_bit); + ClassDB::bind_method(D_METHOD("set_collision_mask_value", "layer_number", "value"), &SoftBody3D::set_collision_mask_value); + ClassDB::bind_method(D_METHOD("get_collision_mask_value", "layer_number"), &SoftBody3D::get_collision_mask_value); - ClassDB::bind_method(D_METHOD("set_collision_layer_bit", "bit", "value"), &SoftBody3D::set_collision_layer_bit); - ClassDB::bind_method(D_METHOD("get_collision_layer_bit", "bit"), &SoftBody3D::get_collision_layer_bit); + ClassDB::bind_method(D_METHOD("set_collision_layer_value", "layer_number", "value"), &SoftBody3D::set_collision_layer_value); + ClassDB::bind_method(D_METHOD("get_collision_layer_value", "layer_number"), &SoftBody3D::get_collision_layer_value); ClassDB::bind_method(D_METHOD("set_parent_collision_ignore", "parent_collision_ignore"), &SoftBody3D::set_parent_collision_ignore); ClassDB::bind_method(D_METHOD("get_parent_collision_ignore"), &SoftBody3D::get_parent_collision_ignore); @@ -361,6 +355,11 @@ void SoftBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_drag_coefficient", "drag_coefficient"), &SoftBody3D::set_drag_coefficient); ClassDB::bind_method(D_METHOD("get_drag_coefficient"), &SoftBody3D::get_drag_coefficient); + ClassDB::bind_method(D_METHOD("get_point_transform", "point_index"), &SoftBody3D::get_point_transform); + + ClassDB::bind_method(D_METHOD("set_point_pinned", "point_index", "pinned", "attachment_path"), &SoftBody3D::pin_point, DEFVAL(NodePath())); + ClassDB::bind_method(D_METHOD("is_point_pinned", "point_index"), &SoftBody3D::is_point_pinned); + ClassDB::bind_method(D_METHOD("set_ray_pickable", "ray_pickable"), &SoftBody3D::set_ray_pickable); ClassDB::bind_method(D_METHOD("is_ray_pickable"), &SoftBody3D::is_ray_pickable); @@ -425,8 +424,8 @@ void SoftBody3D::_draw_soft_mesh() { /// Necessary in order to render the mesh correctly (Soft body nodes are in global space) simulation_started = true; - call_deferred("set_as_top_level", true); - call_deferred("set_transform", Transform3D()); + call_deferred(SNAME("set_as_top_level"), true); + call_deferred(SNAME("set_transform"), Transform3D()); } _update_physics_server(); @@ -515,36 +514,40 @@ uint32_t SoftBody3D::get_collision_layer() const { return collision_layer; } -void SoftBody3D::set_collision_mask_bit(int p_bit, bool p_value) { - ERR_FAIL_INDEX_MSG(p_bit, 32, "Collision mask bit must be between 0 and 31 inclusive."); - uint32_t mask = get_collision_mask(); +void SoftBody3D::set_collision_layer_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive."); + uint32_t collision_layer = get_collision_layer(); if (p_value) { - mask |= 1 << p_bit; + collision_layer |= 1 << (p_layer_number - 1); } else { - mask &= ~(1 << p_bit); + collision_layer &= ~(1 << (p_layer_number - 1)); } - set_collision_mask(mask); + set_collision_layer(collision_layer); } -bool SoftBody3D::get_collision_mask_bit(int p_bit) const { - ERR_FAIL_INDEX_V_MSG(p_bit, 32, false, "Collision mask bit must be between 0 and 31 inclusive."); - return get_collision_mask() & (1 << p_bit); +bool SoftBody3D::get_collision_layer_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Collision layer number must be between 1 and 32 inclusive."); + return get_collision_layer() & (1 << (p_layer_number - 1)); } -void SoftBody3D::set_collision_layer_bit(int p_bit, bool p_value) { - ERR_FAIL_INDEX_MSG(p_bit, 32, "Collision layer bit must be between 0 and 31 inclusive."); - uint32_t layer = get_collision_layer(); +void SoftBody3D::set_collision_mask_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive."); + uint32_t mask = get_collision_mask(); if (p_value) { - layer |= 1 << p_bit; + mask |= 1 << (p_layer_number - 1); } else { - layer &= ~(1 << p_bit); + mask &= ~(1 << (p_layer_number - 1)); } - set_collision_layer(layer); + set_collision_mask(mask); } -bool SoftBody3D::get_collision_layer_bit(int p_bit) const { - ERR_FAIL_INDEX_V_MSG(p_bit, 32, false, "Collision layer bit must be between 0 and 31 inclusive."); - return get_collision_layer() & (1 << p_bit); +bool SoftBody3D::get_collision_mask_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Collision layer number must be between 1 and 32 inclusive."); + return get_collision_mask() & (1 << (p_layer_number - 1)); } void SoftBody3D::set_disable_mode(DisableMode p_mode) { @@ -592,8 +595,7 @@ Array SoftBody3D::get_collision_exceptions() { List<RID> exceptions; PhysicsServer3D::get_singleton()->soft_body_get_collision_exceptions(physics_rid, &exceptions); Array ret; - for (List<RID>::Element *E = exceptions.front(); E; E = E->next()) { - RID body = E->get(); + for (const RID &body : exceptions) { ObjectID instance_id = PhysicsServer3D::get_singleton()->body_get_object_instance_id(body); Object *obj = ObjectDB::get_instance(instance_id); PhysicsBody3D *physics_body = Object::cast_to<PhysicsBody3D>(obj); diff --git a/scene/3d/soft_body_3d.h b/scene/3d/soft_body_3d.h index 81aa0c10c6..46b185a32c 100644 --- a/scene/3d/soft_body_3d.h +++ b/scene/3d/soft_body_3d.h @@ -138,11 +138,11 @@ public: void set_collision_layer(uint32_t p_layer); uint32_t get_collision_layer() const; - void set_collision_mask_bit(int p_bit, bool p_value); - bool get_collision_mask_bit(int p_bit) const; + void set_collision_layer_value(int p_layer_number, bool p_value); + bool get_collision_layer_value(int p_layer_number) const; - void set_collision_layer_bit(int p_bit, bool p_value); - bool get_collision_layer_bit(int p_bit) const; + void set_collision_mask_value(int p_layer_number, bool p_value); + bool get_collision_mask_value(int p_layer_number) const; void set_disable_mode(DisableMode p_mode); DisableMode get_disable_mode() const; diff --git a/scene/3d/spring_arm_3d.cpp b/scene/3d/spring_arm_3d.cpp index 1911e14d54..4748a9d889 100644 --- a/scene/3d/spring_arm_3d.cpp +++ b/scene/3d/spring_arm_3d.cpp @@ -30,11 +30,6 @@ #include "spring_arm_3d.h" -#include "core/config/engine.h" -#include "scene/3d/collision_object_3d.h" -#include "scene/resources/sphere_shape_3d.h" -#include "servers/physics_server_3d.h" - void SpringArm3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: @@ -84,7 +79,7 @@ real_t SpringArm3D::get_length() const { void SpringArm3D::set_length(real_t p_length) { if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_collisions_hint())) { - update_gizmo(); + update_gizmos(); } spring_length = p_length; diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp index 13f8002721..b9a2736918 100644 --- a/scene/3d/sprite_3d.cpp +++ b/scene/3d/sprite_3d.cpp @@ -60,8 +60,8 @@ void SpriteBase3D::_propagate_color_changed() { color_dirty = true; _queue_update(); - for (List<SpriteBase3D *>::Element *E = children.front(); E; E = E->next()) { - E->get()->_propagate_color_changed(); + for (SpriteBase3D *&E : children) { + E->_propagate_color_changed(); } } @@ -132,12 +132,12 @@ Color SpriteBase3D::get_modulate() const { return modulate; } -void SpriteBase3D::set_pixel_size(float p_amount) { +void SpriteBase3D::set_pixel_size(real_t p_amount) { pixel_size = p_amount; _queue_update(); } -float SpriteBase3D::get_pixel_size() const { +real_t SpriteBase3D::get_pixel_size() const { return pixel_size; } @@ -174,7 +174,7 @@ void SpriteBase3D::_queue_update() { } triangle_mesh.unref(); - update_gizmo(); + update_gizmos(); pending_update = true; call_deferred(SceneStringNames::get_singleton()->_im_update); @@ -203,7 +203,7 @@ Ref<TriangleMesh> SpriteBase3D::generate_triangle_mesh() const { return Ref<TriangleMesh>(); } - float pixel_size = get_pixel_size(); + real_t pixel_size = get_pixel_size(); Vector2 vertices[4] = { @@ -470,7 +470,7 @@ void Sprite3D::_draw() { Color color = _get_color_accum(); color.a *= get_opacity(); - float pixel_size = get_pixel_size(); + real_t pixel_size = get_pixel_size(); Vector2 vertices[4] = { @@ -583,10 +583,10 @@ void Sprite3D::_draw() { aabb.expand_to(vtx); } - float v_uv[2] = { uvs[i].x, uvs[i].y }; + float v_uv[2] = { (float)uvs[i].x, (float)uvs[i].y }; memcpy(&attribute_write_buffer[i * attrib_stride + mesh_surface_offsets[RS::ARRAY_TEX_UV]], v_uv, 8); - float v_vertex[3] = { vtx.x, vtx.y, vtx.z }; + float v_vertex[3] = { (float)vtx.x, (float)vtx.y, (float)vtx.z }; memcpy(&vertex_write_buffer[i * vertex_stride + mesh_surface_offsets[RS::ARRAY_VERTEX]], &v_vertex, sizeof(float) * 3); memcpy(&vertex_write_buffer[i * vertex_stride + mesh_surface_offsets[RS::ARRAY_NORMAL]], &v_normal, 4); @@ -837,7 +837,7 @@ void AnimatedSprite3D::_draw() { Color color = _get_color_accum(); color.a *= get_opacity(); - float pixel_size = get_pixel_size(); + real_t pixel_size = get_pixel_size(); Vector2 vertices[4] = { @@ -949,10 +949,10 @@ void AnimatedSprite3D::_draw() { aabb.expand_to(vtx); } - float v_uv[2] = { uvs[i].x, uvs[i].y }; + float v_uv[2] = { (float)uvs[i].x, (float)uvs[i].y }; memcpy(&attribute_write_buffer[i * attrib_stride + mesh_surface_offsets[RS::ARRAY_TEX_UV]], v_uv, 8); - float v_vertex[3] = { vtx.x, vtx.y, vtx.z }; + float v_vertex[3] = { (float)vtx.x, (float)vtx.y, (float)vtx.z }; memcpy(&vertex_write_buffer[i * vertex_stride + mesh_surface_offsets[RS::ARRAY_VERTEX]], &v_vertex, sizeof(float) * 3); memcpy(&vertex_write_buffer[i * vertex_stride + mesh_surface_offsets[RS::ARRAY_NORMAL]], &v_normal, 4); memcpy(&vertex_write_buffer[i * vertex_stride + mesh_surface_offsets[RS::ARRAY_TANGENT]], &v_tangent, 4); @@ -996,7 +996,7 @@ void AnimatedSprite3D::_validate_property(PropertyInfo &property) const { } property.hint_string += String(E->get()); - if (animation == E->get()) { + if (animation == E) { current_found = true; } } @@ -1037,7 +1037,7 @@ void AnimatedSprite3D::_notification(int p_what) { return; //do nothing } - float remaining = get_process_delta_time(); + double remaining = get_process_delta_time(); while (remaining) { if (timeout <= 0) { @@ -1059,7 +1059,7 @@ void AnimatedSprite3D::_notification(int p_what) { emit_signal(SceneStringNames::get_singleton()->frame_changed); } - float to_process = MIN(timeout, remaining); + double to_process = MIN(timeout, remaining); remaining -= to_process; timeout -= to_process; } @@ -1177,7 +1177,7 @@ void AnimatedSprite3D::stop() { } bool AnimatedSprite3D::is_playing() const { - return is_processing(); + return playing; } void AnimatedSprite3D::_reset_timeout() { diff --git a/scene/3d/sprite_3d.h b/scene/3d/sprite_3d.h index 404ef57e6a..90c2a309e1 100644 --- a/scene/3d/sprite_3d.h +++ b/scene/3d/sprite_3d.h @@ -31,8 +31,8 @@ #ifndef SPRITE_3D_H #define SPRITE_3D_H -#include "scene/2d/animated_sprite_2d.h" #include "scene/3d/visual_instance_3d.h" +#include "scene/resources/sprite_frames.h" class SpriteBase3D : public GeometryInstance3D { GDCLASS(SpriteBase3D, GeometryInstance3D); @@ -72,7 +72,7 @@ private: float opacity = 1.0; Vector3::Axis axis = Vector3::AXIS_Z; - float pixel_size = 0.01; + real_t pixel_size = 0.01; AABB aabb; RID mesh; @@ -130,8 +130,8 @@ public: void set_opacity(float p_amount); float get_opacity() const; - void set_pixel_size(float p_amount); - float get_pixel_size() const; + void set_pixel_size(real_t p_amount); + real_t get_pixel_size() const; void set_axis(Vector3::Axis p_axis); Vector3::Axis get_axis() const; @@ -213,7 +213,7 @@ class AnimatedSprite3D : public SpriteBase3D { bool centered = false; - float timeout = 0; + double timeout = 0.0; void _res_changed(); diff --git a/scene/3d/vehicle_body_3d.cpp b/scene/3d/vehicle_body_3d.cpp index b0e37b81a5..daeea81891 100644 --- a/scene/3d/vehicle_body_3d.cpp +++ b/scene/3d/vehicle_body_3d.cpp @@ -149,7 +149,7 @@ void VehicleWheel3D::_update(PhysicsDirectBodyState3D *s) { void VehicleWheel3D::set_radius(real_t p_radius) { m_wheelRadius = p_radius; - update_gizmo(); + update_gizmos(); } real_t VehicleWheel3D::get_radius() const { @@ -158,7 +158,7 @@ real_t VehicleWheel3D::get_radius() const { void VehicleWheel3D::set_suspension_rest_length(real_t p_length) { m_suspensionRestLength = p_length; - update_gizmo(); + update_gizmos(); } real_t VehicleWheel3D::get_suspension_rest_length() const { @@ -802,24 +802,21 @@ void VehicleBody3D::_update_friction(PhysicsDirectBodyState3D *s) { } } -void VehicleBody3D::_direct_state_changed(Object *p_state) { - RigidBody3D::_direct_state_changed(p_state); +void VehicleBody3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) { + RigidBody3D::_body_state_changed(p_state); - state = Object::cast_to<PhysicsDirectBodyState3D>(p_state); - ERR_FAIL_NULL_MSG(state, "Method '_direct_state_changed' must receive a valid PhysicsDirectBodyState3D object as argument"); - - real_t step = state->get_step(); + real_t step = p_state->get_step(); for (int i = 0; i < wheels.size(); i++) { - _update_wheel(i, state); + _update_wheel(i, p_state); } for (int i = 0; i < wheels.size(); i++) { - _ray_cast(i, state); - wheels[i]->set_transform(state->get_transform().inverse() * wheels[i]->m_worldTransform); + _ray_cast(i, p_state); + wheels[i]->set_transform(p_state->get_transform().inverse() * wheels[i]->m_worldTransform); } - _update_suspension(state); + _update_suspension(p_state); for (int i = 0; i < wheels.size(); i++) { //apply suspension force @@ -831,20 +828,20 @@ void VehicleBody3D::_direct_state_changed(Object *p_state) { suspensionForce = wheel.m_maxSuspensionForce; } Vector3 impulse = wheel.m_raycastInfo.m_contactNormalWS * suspensionForce * step; - Vector3 relative_position = wheel.m_raycastInfo.m_contactPointWS - state->get_transform().origin; + Vector3 relative_position = wheel.m_raycastInfo.m_contactPointWS - p_state->get_transform().origin; - state->apply_impulse(impulse, relative_position); + p_state->apply_impulse(impulse, relative_position); } - _update_friction(state); + _update_friction(p_state); for (int i = 0; i < wheels.size(); i++) { VehicleWheel3D &wheel = *wheels[i]; - Vector3 relpos = wheel.m_raycastInfo.m_hardPointWS - state->get_transform().origin; - Vector3 vel = state->get_linear_velocity() + (state->get_angular_velocity()).cross(relpos); // * mPos); + Vector3 relpos = wheel.m_raycastInfo.m_hardPointWS - p_state->get_transform().origin; + Vector3 vel = p_state->get_linear_velocity() + (p_state->get_angular_velocity()).cross(relpos); // * mPos); if (wheel.m_raycastInfo.m_isInContact) { - const Transform3D &chassisWorldTransform = state->get_transform(); + const Transform3D &chassisWorldTransform = p_state->get_transform(); Vector3 fwd( chassisWorldTransform.basis[0][Vector3::AXIS_Z], @@ -864,8 +861,6 @@ void VehicleBody3D::_direct_state_changed(Object *p_state) { wheel.m_deltaRotation *= real_t(0.99); //damping of rotation when not in contact } - - state = nullptr; } void VehicleBody3D::set_engine_force(real_t p_engine_force) { @@ -926,7 +921,5 @@ void VehicleBody3D::_bind_methods() { VehicleBody3D::VehicleBody3D() { exclude.insert(get_rid()); - //PhysicsServer3D::get_singleton()->body_set_force_integration_callback(get_rid(), callable_mp(this, &VehicleBody3D::_direct_state_changed)); - set_mass(40); } diff --git a/scene/3d/vehicle_body_3d.h b/scene/3d/vehicle_body_3d.h index 2c10205ea3..f29c3d89b7 100644 --- a/scene/3d/vehicle_body_3d.h +++ b/scene/3d/vehicle_body_3d.h @@ -192,7 +192,8 @@ class VehicleBody3D : public RigidBody3D { static void _bind_methods(); - void _direct_state_changed(Object *p_state) override; + static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state); + virtual void _body_state_changed(PhysicsDirectBodyState3D *p_state) override; public: void set_engine_force(real_t p_engine_force); diff --git a/scene/3d/velocity_tracker_3d.cpp b/scene/3d/velocity_tracker_3d.cpp index 5b5cc06456..200664a41b 100644 --- a/scene/3d/velocity_tracker_3d.cpp +++ b/scene/3d/velocity_tracker_3d.cpp @@ -29,7 +29,6 @@ /*************************************************************************/ #include "velocity_tracker_3d.h" -#include "core/config/engine.h" void VelocityTracker3D::set_track_physics_step(bool p_track_physics_step) { physics_step = p_track_physics_step; @@ -61,16 +60,16 @@ void VelocityTracker3D::update_position(const Vector3 &p_position) { Vector3 VelocityTracker3D::get_tracked_linear_velocity() const { Vector3 linear_velocity; - float max_time = 1 / 5.0; //maximum time to interpolate a velocity + double max_time = 1 / 5.0; //maximum time to interpolate a velocity Vector3 distance_accum; - float time_accum = 0.0; - float base_time = 0.0; + double time_accum = 0.0; + double base_time = 0.0; if (position_history_len) { if (physics_step) { uint64_t base = Engine::get_singleton()->get_physics_frames(); - base_time = float(base - position_history[0].frame) / Engine::get_singleton()->get_iterations_per_second(); + base_time = double(base - position_history[0].frame) / Engine::get_singleton()->get_physics_ticks_per_second(); } else { uint64_t base = Engine::get_singleton()->get_frame_ticks(); base_time = double(base - position_history[0].frame) / 1000000.0; @@ -78,12 +77,12 @@ Vector3 VelocityTracker3D::get_tracked_linear_velocity() const { } for (int i = 0; i < position_history_len - 1; i++) { - float delta = 0.0; + double delta = 0.0; uint64_t diff = position_history[i].frame - position_history[i + 1].frame; Vector3 distance = position_history[i].position - position_history[i + 1].position; if (physics_step) { - delta = float(diff) / Engine::get_singleton()->get_iterations_per_second(); + delta = double(diff) / Engine::get_singleton()->get_physics_ticks_per_second(); } else { delta = double(diff) / 1000000.0; } diff --git a/scene/3d/visible_on_screen_notifier_3d.cpp b/scene/3d/visible_on_screen_notifier_3d.cpp index 682bcec449..3d0bc3df9c 100644 --- a/scene/3d/visible_on_screen_notifier_3d.cpp +++ b/scene/3d/visible_on_screen_notifier_3d.cpp @@ -30,10 +30,6 @@ #include "visible_on_screen_notifier_3d.h" -#include "core/config/engine.h" -#include "scene/3d/camera_3d.h" -#include "scene/3d/physics_body_3d.h" -#include "scene/animation/animation_player.h" #include "scene/scene_string_names.h" void VisibleOnScreenNotifier3D::_visibility_enter() { @@ -63,7 +59,7 @@ void VisibleOnScreenNotifier3D::set_aabb(const AABB &p_aabb) { RS::get_singleton()->visibility_notifier_set_aabb(get_base(), aabb); - update_gizmo(); + update_gizmos(); } AABB VisibleOnScreenNotifier3D::get_aabb() const { diff --git a/scene/3d/visual_instance_3d.cpp b/scene/3d/visual_instance_3d.cpp index 471dc03d62..73c2887983 100644 --- a/scene/3d/visual_instance_3d.cpp +++ b/scene/3d/visual_instance_3d.cpp @@ -31,8 +31,6 @@ #include "visual_instance_3d.h" #include "scene/scene_string_names.h" -#include "servers/rendering_server.h" -#include "skeleton_3d.h" AABB VisualInstance3D::get_transformed_aabb() const { return get_global_transform().xform(get_aabb()); @@ -93,18 +91,22 @@ uint32_t VisualInstance3D::get_layer_mask() const { return layers; } -void VisualInstance3D::set_layer_mask_bit(int p_layer, bool p_enable) { - ERR_FAIL_INDEX(p_layer, 32); - if (p_enable) { - set_layer_mask(layers | (1 << p_layer)); +void VisualInstance3D::set_layer_mask_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Render layer number must be between 1 and 20 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 20, "Render layer number must be between 1 and 20 inclusive."); + uint32_t mask = get_layer_mask(); + if (p_value) { + mask |= 1 << (p_layer_number - 1); } else { - set_layer_mask(layers & (~(1 << p_layer))); + mask &= ~(1 << (p_layer_number - 1)); } + set_layer_mask(mask); } -bool VisualInstance3D::get_layer_mask_bit(int p_layer) const { - ERR_FAIL_INDEX_V(p_layer, 32, false); - return (layers & (1 << p_layer)); +bool VisualInstance3D::get_layer_mask_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Render layer number must be between 1 and 20 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 20, false, "Render layer number must be between 1 and 20 inclusive."); + return layers & (1 << (p_layer_number - 1)); } void VisualInstance3D::_bind_methods() { @@ -114,8 +116,8 @@ void VisualInstance3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_instance"), &VisualInstance3D::get_instance); ClassDB::bind_method(D_METHOD("set_layer_mask", "mask"), &VisualInstance3D::set_layer_mask); ClassDB::bind_method(D_METHOD("get_layer_mask"), &VisualInstance3D::get_layer_mask); - ClassDB::bind_method(D_METHOD("set_layer_mask_bit", "layer", "enabled"), &VisualInstance3D::set_layer_mask_bit); - ClassDB::bind_method(D_METHOD("get_layer_mask_bit", "layer"), &VisualInstance3D::get_layer_mask_bit); + ClassDB::bind_method(D_METHOD("set_layer_mask_value", "layer_number", "value"), &VisualInstance3D::set_layer_mask_value); + ClassDB::bind_method(D_METHOD("get_layer_mask_value", "layer_number"), &VisualInstance3D::get_layer_mask_value); ClassDB::bind_method(D_METHOD("get_transformed_aabb"), &VisualInstance3D::get_transformed_aabb); @@ -153,6 +155,7 @@ Ref<Material> GeometryInstance3D::get_material_override() const { void GeometryInstance3D::set_visibility_range_begin(float p_dist) { visibility_range_begin = p_dist; RS::get_singleton()->instance_geometry_set_visibility_range(get_instance(), visibility_range_begin, visibility_range_end, visibility_range_begin_margin, visibility_range_end_margin); + update_configuration_warnings(); } float GeometryInstance3D::get_visibility_range_begin() const { @@ -162,6 +165,7 @@ float GeometryInstance3D::get_visibility_range_begin() const { void GeometryInstance3D::set_visibility_range_end(float p_dist) { visibility_range_end = p_dist; RS::get_singleton()->instance_geometry_set_visibility_range(get_instance(), visibility_range_begin, visibility_range_end, visibility_range_begin_margin, visibility_range_end_margin); + update_configuration_warnings(); } float GeometryInstance3D::get_visibility_range_end() const { @@ -238,8 +242,7 @@ bool GeometryInstance3D::_get(const StringName &p_name, Variant &r_ret) const { void GeometryInstance3D::_get_property_list(List<PropertyInfo> *p_list) const { List<PropertyInfo> pinfo; RS::get_singleton()->instance_geometry_get_shader_parameter_list(get_instance(), &pinfo); - for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) { - PropertyInfo pi = E->get(); + for (PropertyInfo &pi : pinfo) { bool has_def_value = false; Variant def_value = RS::get_singleton()->instance_geometry_get_shader_parameter_default_value(get_instance(), pi.name); if (def_value.get_type() != Variant::NIL) { @@ -293,7 +296,12 @@ void GeometryInstance3D::set_shader_instance_uniform(const StringName &p_uniform instance_uniforms.erase(p_value); } else { instance_uniforms[p_uniform] = p_value; - RS::get_singleton()->instance_geometry_set_shader_parameter(get_instance(), p_uniform, p_value); + if (p_value.get_type() == Variant::OBJECT) { + RID tex_id = p_value; + RS::get_singleton()->instance_geometry_set_shader_parameter(get_instance(), p_uniform, tex_id); + } else { + RS::get_singleton()->instance_geometry_set_shader_parameter(get_instance(), p_uniform, p_value); + } } } @@ -347,6 +355,16 @@ bool GeometryInstance3D::is_ignoring_occlusion_culling() { return ignore_occlusion_culling; } +TypedArray<String> GeometryInstance3D::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); + + if (!Math::is_zero_approx(visibility_range_end) && visibility_range_end <= visibility_range_begin) { + warnings.push_back(TTR("The GeometryInstance3D visibility range's End distance is set to a non-zero value, but is lower than the Begin distance.\nThis means the GeometryInstance3D will never be visible.\nTo resolve this, set the End distance to 0 or to a value greater than the Begin distance.")); + } + + return warnings; +} + void GeometryInstance3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_material_override", "material"), &GeometryInstance3D::set_material_override); ClassDB::bind_method(D_METHOD("get_material_override"), &GeometryInstance3D::get_material_override); @@ -389,14 +407,14 @@ void GeometryInstance3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_aabb"), &GeometryInstance3D::get_aabb); ADD_GROUP("Geometry", ""); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material_override", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,StandardMaterial3D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DEFERRED_SET_RESOURCE), "set_material_override", "get_material_override"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material_override", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DEFERRED_SET_RESOURCE), "set_material_override", "get_material_override"); ADD_PROPERTY(PropertyInfo(Variant::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, "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_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "gi_mode", PROPERTY_HINT_ENUM, "Disabled,Baked,Dynamic"), "set_gi_mode", "get_gi_mode"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "gi_lightmap_scale", PROPERTY_HINT_ENUM, "1x,2x,4x,8x"), "set_lightmap_scale", "get_lightmap_scale"); + 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"); diff --git a/scene/3d/visual_instance_3d.h b/scene/3d/visual_instance_3d.h index 2d5699859b..8d24e13d47 100644 --- a/scene/3d/visual_instance_3d.h +++ b/scene/3d/visual_instance_3d.h @@ -31,10 +31,7 @@ #ifndef VISUAL_INSTANCE_H #define VISUAL_INSTANCE_H -#include "core/math/face3.h" -#include "core/templates/rid.h" #include "scene/3d/node_3d.h" -#include "scene/resources/material.h" class VisualInstance3D : public Node3D { GDCLASS(VisualInstance3D, Node3D); @@ -72,8 +69,8 @@ public: void set_layer_mask(uint32_t p_mask); uint32_t get_layer_mask() const; - void set_layer_mask_bit(int p_layer, bool p_enable); - bool get_layer_mask_bit(int p_layer) const; + void set_layer_mask_value(int p_layer_number, bool p_enable); + bool get_layer_mask_value(int p_layer_number) const; VisualInstance3D(); ~VisualInstance3D(); @@ -177,6 +174,7 @@ public: void set_ignore_occlusion_culling(bool p_enabled); bool is_ignoring_occlusion_culling(); + TypedArray<String> get_configuration_warnings() const override; GeometryInstance3D(); }; diff --git a/scene/3d/voxel_gi.cpp b/scene/3d/voxel_gi.cpp index 3da59ac4c0..d3d12d94e9 100644 --- a/scene/3d/voxel_gi.cpp +++ b/scene/3d/voxel_gi.cpp @@ -30,9 +30,8 @@ #include "voxel_gi.h" -#include "core/os/os.h" - #include "mesh_instance_3d.h" +#include "multimesh_instance_3d.h" #include "voxelizer.h" void VoxelGIData::_set_data(const Dictionary &p_data) { @@ -265,7 +264,7 @@ Ref<VoxelGIData> VoxelGI::get_probe_data() const { void VoxelGI::set_subdiv(Subdiv p_subdiv) { ERR_FAIL_INDEX(p_subdiv, SUBDIV_MAX); subdiv = p_subdiv; - update_gizmo(); + update_gizmos(); } VoxelGI::Subdiv VoxelGI::get_subdiv() const { @@ -274,7 +273,7 @@ VoxelGI::Subdiv VoxelGI::get_subdiv() const { void VoxelGI::set_extents(const Vector3 &p_extents) { extents = p_extents; - update_gizmo(); + update_gizmos(); } Vector3 VoxelGI::get_extents() const { @@ -384,14 +383,14 @@ void VoxelGI::bake(Node *p_from_node, bool p_create_visual_debug) { int pmc = 0; - for (List<PlotMesh>::Element *E = mesh_list.front(); E; E = E->next()) { + for (PlotMesh &E : mesh_list) { if (bake_step_function) { bake_step_function(pmc, RTR("Plotting Meshes") + " " + itos(pmc) + "/" + itos(mesh_list.size())); } pmc++; - baker.plot_mesh(E->get().local_xform, E->get().mesh, E->get().instance_materials, E->get().override_material); + baker.plot_mesh(E.local_xform, E.mesh, E.instance_materials, E.override_material); } if (bake_step_function) { bake_step_function(pmc++, RTR("Finishing Plot")); diff --git a/scene/3d/voxel_gi.h b/scene/3d/voxel_gi.h index 434d209421..5d0dda1ba3 100644 --- a/scene/3d/voxel_gi.h +++ b/scene/3d/voxel_gi.h @@ -31,7 +31,6 @@ #ifndef VOXEL_GI_H #define VOXEL_GI_H -#include "multimesh_instance_3d.h" #include "scene/3d/visual_instance_3d.h" class VoxelGIData : public Resource { diff --git a/scene/3d/voxelizer.cpp b/scene/3d/voxelizer.cpp index 12f055c01d..2d32379d69 100644 --- a/scene/3d/voxelizer.cpp +++ b/scene/3d/voxelizer.cpp @@ -29,24 +29,19 @@ /*************************************************************************/ #include "voxelizer.h" -#include "core/math/geometry_3d.h" -#include "core/os/os.h" -#include "core/os/threaded_array_processor.h" - -#include <stdlib.h> static _FORCE_INLINE_ void get_uv_and_normal(const Vector3 &p_pos, const Vector3 *p_vtx, const Vector2 *p_uv, const Vector3 *p_normal, Vector2 &r_uv, Vector3 &r_normal) { - if (p_pos.distance_squared_to(p_vtx[0]) < CMP_EPSILON2) { + if (p_pos.is_equal_approx(p_vtx[0])) { r_uv = p_uv[0]; r_normal = p_normal[0]; return; } - if (p_pos.distance_squared_to(p_vtx[1]) < CMP_EPSILON2) { + if (p_pos.is_equal_approx(p_vtx[1])) { r_uv = p_uv[1]; r_normal = p_normal[1]; return; } - if (p_pos.distance_squared_to(p_vtx[2]) < CMP_EPSILON2) { + if (p_pos.is_equal_approx(p_vtx[2])) { r_uv = p_uv[2]; r_normal = p_normal[2]; return; @@ -56,20 +51,20 @@ static _FORCE_INLINE_ void get_uv_and_normal(const Vector3 &p_pos, const Vector3 Vector3 v1 = p_vtx[2] - p_vtx[0]; Vector3 v2 = p_pos - p_vtx[0]; - float d00 = v0.dot(v0); - float d01 = v0.dot(v1); - float d11 = v1.dot(v1); - float d20 = v2.dot(v0); - float d21 = v2.dot(v1); - float denom = (d00 * d11 - d01 * d01); + real_t d00 = v0.dot(v0); + real_t d01 = v0.dot(v1); + real_t d11 = v1.dot(v1); + real_t d20 = v2.dot(v0); + real_t d21 = v2.dot(v1); + real_t denom = (d00 * d11 - d01 * d01); if (denom == 0) { r_uv = p_uv[0]; r_normal = p_normal[0]; return; } - float v = (d11 * d20 - d01 * d21) / denom; - float w = (d00 * d21 - d01 * d20) / denom; - float u = 1.0f - v - w; + real_t v = (d11 * d20 - d01 * d21) / denom; + real_t w = (d00 * d21 - d01 * d20) / denom; + real_t u = 1.0f - v - w; r_uv = p_uv[0] * u + p_uv[1] * v + p_uv[2] * w; r_normal = (p_normal[0] * u + p_normal[1] * v + p_normal[2] * w).normalized(); @@ -81,7 +76,7 @@ void Voxelizer::_plot_face(int p_idx, int p_level, int p_x, int p_y, int p_z, co //find best axis to map to, for scanning values int closest_axis = 0; - float closest_dot = 0; + real_t closest_dot = 0; Plane plane = Plane(p_vtx[0], p_vtx[1], p_vtx[2]); Vector3 normal = plane.normal; @@ -89,7 +84,7 @@ void Voxelizer::_plot_face(int p_idx, int p_level, int p_x, int p_y, int p_z, co for (int i = 0; i < 3; i++) { Vector3 axis; axis[i] = 1.0; - float dot = ABS(normal.dot(axis)); + real_t dot = ABS(normal.dot(axis)); if (i == 0 || dot > closest_dot) { closest_axis = i; closest_dot = dot; @@ -103,8 +98,8 @@ void Voxelizer::_plot_face(int p_idx, int p_level, int p_x, int p_y, int p_z, co Vector3 t2; t2[(closest_axis + 2) % 3] = 1.0; - t1 *= p_aabb.size[(closest_axis + 1) % 3] / float(color_scan_cell_width); - t2 *= p_aabb.size[(closest_axis + 2) % 3] / float(color_scan_cell_width); + t1 *= p_aabb.size[(closest_axis + 1) % 3] / real_t(color_scan_cell_width); + t2 *= p_aabb.size[(closest_axis + 2) % 3] / real_t(color_scan_cell_width); Color albedo_accum; Color emission_accum; @@ -114,10 +109,10 @@ void Voxelizer::_plot_face(int p_idx, int p_level, int p_x, int p_y, int p_z, co //map to a grid average in the best axis for this face for (int i = 0; i < color_scan_cell_width; i++) { - Vector3 ofs_i = float(i) * t1; + Vector3 ofs_i = real_t(i) * t1; for (int j = 0; j < color_scan_cell_width; j++) { - Vector3 ofs_j = float(j) * t2; + Vector3 ofs_j = real_t(j) * t2; Vector3 from = p_aabb.position + ofs_i + ofs_j; Vector3 to = from + t1 + t2 + axis * p_aabb.size[closest_axis]; @@ -155,8 +150,8 @@ void Voxelizer::_plot_face(int p_idx, int p_level, int p_x, int p_y, int p_z, co lnormal = normal; } - int uv_x = CLAMP(int(Math::fposmod(uv.x, 1.0f) * bake_texture_size), 0, bake_texture_size - 1); - int uv_y = CLAMP(int(Math::fposmod(uv.y, 1.0f) * bake_texture_size), 0, bake_texture_size - 1); + int uv_x = CLAMP(int(Math::fposmod(uv.x, (real_t)1.0) * bake_texture_size), 0, bake_texture_size - 1); + int uv_y = CLAMP(int(Math::fposmod(uv.y, (real_t)1.0) * bake_texture_size), 0, bake_texture_size - 1); int ofs = uv_y * bake_texture_size + uv_x; albedo_accum.r += p_material.albedo[ofs].r; @@ -187,8 +182,8 @@ void Voxelizer::_plot_face(int p_idx, int p_level, int p_x, int p_y, int p_z, co lnormal = normal; } - int uv_x = CLAMP(Math::fposmod(uv.x, 1.0f) * bake_texture_size, 0, bake_texture_size - 1); - int uv_y = CLAMP(Math::fposmod(uv.y, 1.0f) * bake_texture_size, 0, bake_texture_size - 1); + int uv_x = CLAMP(Math::fposmod(uv.x, (real_t)1.0) * bake_texture_size, 0, bake_texture_size - 1); + int uv_y = CLAMP(Math::fposmod(uv.y, (real_t)1.0) * bake_texture_size, 0, bake_texture_size - 1); int ofs = uv_y * bake_texture_size + uv_x; @@ -636,7 +631,7 @@ void Voxelizer::begin_bake(int p_subdiv, const AABB &p_bounds) { } axis_cell_size[i] = axis_cell_size[longest_axis]; - float axis_size = po2_bounds.size[longest_axis]; + real_t axis_size = po2_bounds.size[longest_axis]; //shrink until fit subdiv while (axis_size / 2.0 >= po2_bounds.size[i]) { @@ -954,7 +949,7 @@ Ref<MultiMesh> Voxelizer::create_debug_multimesh() { Vector3 face_points[4]; for (int j = 0; j < 4; j++) { - float v[3]; + real_t v[3]; v[0] = 1.0; v[1] = 1 - 2 * ((j >> 1) & 1); v[2] = v[1] * (1 - 2 * (j & 1)); diff --git a/scene/3d/voxelizer.h b/scene/3d/voxelizer.h index e500d2d4c3..09c126bc4e 100644 --- a/scene/3d/voxelizer.h +++ b/scene/3d/voxelizer.h @@ -31,8 +31,6 @@ #ifndef VOXEL_LIGHT_BAKER_H #define VOXEL_LIGHT_BAKER_H -#include "core/math/vector3i.h" -#include "scene/3d/mesh_instance_3d.h" #include "scene/resources/multimesh.h" class Voxelizer { diff --git a/scene/3d/world_environment.cpp b/scene/3d/world_environment.cpp index 352bef072f..26fa43b969 100644 --- a/scene/3d/world_environment.cpp +++ b/scene/3d/world_environment.cpp @@ -30,6 +30,7 @@ #include "world_environment.h" +#include "scene/3d/node_3d.h" #include "scene/main/window.h" void WorldEnvironment::_notification(int p_what) { diff --git a/scene/3d/world_environment.h b/scene/3d/world_environment.h index 9e85982381..310d1e96a5 100644 --- a/scene/3d/world_environment.h +++ b/scene/3d/world_environment.h @@ -31,7 +31,7 @@ #ifndef SCENARIO_FX_H #define SCENARIO_FX_H -#include "scene/3d/node_3d.h" +#include "scene/main/node.h" #include "scene/resources/camera_effects.h" #include "scene/resources/environment.h" diff --git a/scene/3d/xr_nodes.cpp b/scene/3d/xr_nodes.cpp index a91e712b0b..ebfb58e9fe 100644 --- a/scene/3d/xr_nodes.cpp +++ b/scene/3d/xr_nodes.cpp @@ -30,9 +30,8 @@ #include "xr_nodes.h" -#include "core/input/input.h" +#include "scene/main/viewport.h" #include "servers/xr/xr_interface.h" -#include "servers/xr_server.h" //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -124,7 +123,7 @@ Point2 XRCamera3D::unproject_position(const Vector3 &p_pos) const { return res; }; -Vector3 XRCamera3D::project_position(const Point2 &p_point, float p_z_depth) const { +Vector3 XRCamera3D::project_position(const Point2 &p_point, real_t p_z_depth) const { // get our XRServer XRServer *xr_server = XRServer::get_singleton(); ERR_FAIL_NULL_V(xr_server, Vector3()); @@ -207,10 +206,10 @@ void XRController3D::_notification(int p_what) { bool is_pressed = Input::get_singleton()->is_joy_button_pressed(joy_id, (JoyButton)i); if (!was_pressed && is_pressed) { - emit_signal("button_pressed", i); + emit_signal(SNAME("button_pressed"), i); button_states += mask; } else if (was_pressed && !is_pressed) { - emit_signal("button_released", i); + emit_signal(SNAME("button_released"), i); button_states -= mask; }; @@ -225,7 +224,7 @@ void XRController3D::_notification(int p_what) { Ref<Mesh> trackerMesh = tracker->get_mesh(); if (mesh != trackerMesh) { mesh = trackerMesh; - emit_signal("mesh_updated", mesh); + emit_signal(SNAME("mesh_updated"), mesh); } }; }; break; @@ -422,7 +421,7 @@ void XRAnchor3D::_notification(int p_what) { Ref<Mesh> trackerMesh = tracker->get_mesh(); if (mesh != trackerMesh) { mesh = trackerMesh; - emit_signal("mesh_updated", mesh); + emit_signal(SNAME("mesh_updated"), mesh); } }; }; break; @@ -544,7 +543,7 @@ void XROrigin3D::clear_tracked_camera_if(XRCamera3D *p_tracked_camera) { }; }; -float XROrigin3D::get_world_scale() const { +real_t XROrigin3D::get_world_scale() const { // get our XRServer XRServer *xr_server = XRServer::get_singleton(); ERR_FAIL_NULL_V(xr_server, 1.0); @@ -552,7 +551,7 @@ float XROrigin3D::get_world_scale() const { return xr_server->get_world_scale(); }; -void XROrigin3D::set_world_scale(float p_world_scale) { +void XROrigin3D::set_world_scale(real_t p_world_scale) { // get our XRServer XRServer *xr_server = XRServer::get_singleton(); ERR_FAIL_NULL(xr_server); diff --git a/scene/3d/xr_nodes.h b/scene/3d/xr_nodes.h index 90079f5fe9..6e54ff83d7 100644 --- a/scene/3d/xr_nodes.h +++ b/scene/3d/xr_nodes.h @@ -32,8 +32,6 @@ #define XR_NODES_H #include "scene/3d/camera_3d.h" -#include "scene/3d/node_3d.h" -#include "scene/resources/mesh.h" #include "servers/xr/xr_positional_tracker.h" /** @@ -54,7 +52,7 @@ public: virtual Vector3 project_local_ray_normal(const Point2 &p_pos) const override; virtual Point2 unproject_position(const Vector3 &p_pos) const override; - virtual Vector3 project_position(const Point2 &p_point, float p_z_depth) const override; + virtual Vector3 project_position(const Point2 &p_point, real_t p_z_depth) const override; virtual Vector<Plane> get_frustum() const override; XRCamera3D() {} @@ -163,8 +161,8 @@ public: void set_tracked_camera(XRCamera3D *p_tracked_camera); void clear_tracked_camera_if(XRCamera3D *p_tracked_camera); - float get_world_scale() const; - void set_world_scale(float p_world_scale); + real_t get_world_scale() const; + void set_world_scale(real_t p_world_scale); XROrigin3D() {} ~XROrigin3D() {} diff --git a/scene/SCsub b/scene/SCsub index ccd2bab8ff..92288211bb 100644 --- a/scene/SCsub +++ b/scene/SCsub @@ -10,7 +10,8 @@ env.add_source_files(env.scene_sources, "*.cpp") # Chain load SCsubs SConscript("main/SCsub") SConscript("gui/SCsub") -SConscript("3d/SCsub") +if not env["disable_3d"]: + SConscript("3d/SCsub") SConscript("2d/SCsub") SConscript("animation/SCsub") SConscript("audio/SCsub") diff --git a/scene/animation/animation_blend_space_1d.cpp b/scene/animation/animation_blend_space_1d.cpp index 3818c7edd1..0167992baf 100644 --- a/scene/animation/animation_blend_space_1d.cpp +++ b/scene/animation/animation_blend_space_1d.cpp @@ -54,7 +54,7 @@ void AnimationNodeBlendSpace1D::_validate_property(PropertyInfo &property) const } void AnimationNodeBlendSpace1D::_tree_changed() { - emit_signal("tree_changed"); + emit_signal(SNAME("tree_changed")); } void AnimationNodeBlendSpace1D::_bind_methods() { @@ -120,7 +120,7 @@ void AnimationNodeBlendSpace1D::add_blend_point(const Ref<AnimationRootNode> &p_ blend_points[p_at_index].node->connect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace1D::_tree_changed), varray(), CONNECT_REFERENCE_COUNTED); blend_points_used++; - emit_signal("tree_changed"); + emit_signal(SNAME("tree_changed")); } void AnimationNodeBlendSpace1D::set_blend_point_position(int p_point, float p_position) { @@ -140,7 +140,7 @@ void AnimationNodeBlendSpace1D::set_blend_point_node(int p_point, const Ref<Anim blend_points[p_point].node = p_node; blend_points[p_point].node->connect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace1D::_tree_changed), varray(), CONNECT_REFERENCE_COUNTED); - emit_signal("tree_changed"); + emit_signal(SNAME("tree_changed")); } float AnimationNodeBlendSpace1D::get_blend_point_position(int p_point) const { @@ -164,7 +164,7 @@ void AnimationNodeBlendSpace1D::remove_blend_point(int p_point) { } blend_points_used--; - emit_signal("tree_changed"); + emit_signal(SNAME("tree_changed")); } int AnimationNodeBlendSpace1D::get_blend_point_count() const { @@ -219,7 +219,7 @@ void AnimationNodeBlendSpace1D::_add_blend_point(int p_index, const Ref<Animatio } } -float AnimationNodeBlendSpace1D::process(float p_time, bool p_seek) { +double AnimationNodeBlendSpace1D::process(double p_time, bool p_seek) { if (blend_points_used == 0) { return 0.0; } @@ -229,7 +229,7 @@ float AnimationNodeBlendSpace1D::process(float p_time, bool p_seek) { return blend_node(blend_points[0].name, blend_points[0].node, p_time, p_seek, 1.0, FILTER_IGNORE, false); } - float blend_pos = get_parameter(blend_position); + double blend_pos = get_parameter(blend_position); float weights[MAX_BLEND_POINTS] = {}; diff --git a/scene/animation/animation_blend_space_1d.h b/scene/animation/animation_blend_space_1d.h index 8886e6c679..6730c09fd4 100644 --- a/scene/animation/animation_blend_space_1d.h +++ b/scene/animation/animation_blend_space_1d.h @@ -93,7 +93,7 @@ public: void set_value_label(const String &p_label); String get_value_label() const; - float process(float p_time, bool p_seek) override; + double process(double p_time, bool p_seek) 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 935ec457aa..145e7c605b 100644 --- a/scene/animation/animation_blend_space_2d.cpp +++ b/scene/animation/animation_blend_space_2d.cpp @@ -84,7 +84,7 @@ void AnimationNodeBlendSpace2D::add_blend_point(const Ref<AnimationRootNode> &p_ _queue_auto_triangles(); - emit_signal("tree_changed"); + emit_signal(SNAME("tree_changed")); } void AnimationNodeBlendSpace2D::set_blend_point_position(int p_point, const Vector2 &p_position) { @@ -103,7 +103,7 @@ void AnimationNodeBlendSpace2D::set_blend_point_node(int p_point, const Ref<Anim blend_points[p_point].node = p_node; blend_points[p_point].node->connect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace2D::_tree_changed), varray(), CONNECT_REFERENCE_COUNTED); - emit_signal("tree_changed"); + emit_signal(SNAME("tree_changed")); } Vector2 AnimationNodeBlendSpace2D::get_blend_point_position(int p_point) const { @@ -143,7 +143,7 @@ void AnimationNodeBlendSpace2D::remove_blend_point(int p_point) { blend_points[i] = blend_points[i + 1]; } blend_points_used--; - emit_signal("tree_changed"); + emit_signal(SNAME("tree_changed")); } int AnimationNodeBlendSpace2D::get_blend_point_count() const { @@ -321,7 +321,7 @@ void AnimationNodeBlendSpace2D::_queue_auto_triangles() { } trianges_dirty = true; - call_deferred("_update_triangles"); + call_deferred(SNAME("_update_triangles")); } void AnimationNodeBlendSpace2D::_update_triangles() { @@ -332,7 +332,7 @@ void AnimationNodeBlendSpace2D::_update_triangles() { trianges_dirty = false; triangles.clear(); if (blend_points_used < 3) { - emit_signal("triangles_updated"); + emit_signal(SNAME("triangles_updated")); return; } @@ -347,7 +347,7 @@ void AnimationNodeBlendSpace2D::_update_triangles() { for (int i = 0; i < triangles.size(); i++) { add_triangle(triangles[i].points[0], triangles[i].points[1], triangles[i].points[2]); } - emit_signal("triangles_updated"); + emit_signal(SNAME("triangles_updated")); } Vector2 AnimationNodeBlendSpace2D::get_closest_point(const Vector2 &p_point) { @@ -387,19 +387,19 @@ Vector2 AnimationNodeBlendSpace2D::get_closest_point(const Vector2 &p_point) { } void AnimationNodeBlendSpace2D::_blend_triangle(const Vector2 &p_pos, const Vector2 *p_points, float *r_weights) { - if (p_pos.distance_squared_to(p_points[0]) < CMP_EPSILON2) { + if (p_pos.is_equal_approx(p_points[0])) { r_weights[0] = 1; r_weights[1] = 0; r_weights[2] = 0; return; } - if (p_pos.distance_squared_to(p_points[1]) < CMP_EPSILON2) { + if (p_pos.is_equal_approx(p_points[1])) { r_weights[0] = 0; r_weights[1] = 1; r_weights[2] = 0; return; } - if (p_pos.distance_squared_to(p_points[2]) < CMP_EPSILON2) { + if (p_pos.is_equal_approx(p_points[2])) { r_weights[0] = 0; r_weights[1] = 0; r_weights[2] = 1; @@ -431,12 +431,12 @@ void AnimationNodeBlendSpace2D::_blend_triangle(const Vector2 &p_pos, const Vect r_weights[2] = w; } -float AnimationNodeBlendSpace2D::process(float p_time, bool p_seek) { +double AnimationNodeBlendSpace2D::process(double p_time, bool p_seek) { _update_triangles(); Vector2 blend_pos = get_parameter(blend_position); int closest = get_parameter(this->closest); - float length_internal = get_parameter(this->length_internal); + double length_internal = get_parameter(this->length_internal); float mind = 0.0; //time of min distance point if (blend_mode == BLEND_MODE_INTERPOLATED) { @@ -532,10 +532,10 @@ float AnimationNodeBlendSpace2D::process(float p_time, bool p_seek) { float from = 0.0; if (blend_mode == BLEND_MODE_DISCRETE_CARRY && closest != -1) { //see how much animation remains - from = blend_node(blend_points[closest].name, blend_points[closest].node, p_time, true, 0.0, FILTER_IGNORE, false) - length_internal; + from = length_internal - blend_node(blend_points[closest].name, blend_points[closest].node, p_time, false, 0.0, FILTER_IGNORE, false); } - mind = blend_node(blend_points[new_closest].name, blend_points[new_closest].node, from, true, 1.0, FILTER_IGNORE, false) + from; + mind = blend_node(blend_points[new_closest].name, blend_points[new_closest].node, from, true, 1.0, FILTER_IGNORE, false); length_internal = from + mind; closest = new_closest; @@ -586,7 +586,7 @@ Ref<AnimationNode> AnimationNodeBlendSpace2D::get_child_by_name(const StringName } void AnimationNodeBlendSpace2D::_tree_changed() { - emit_signal("tree_changed"); + emit_signal(SNAME("tree_changed")); } void AnimationNodeBlendSpace2D::set_blend_mode(BlendMode p_blend_mode) { diff --git a/scene/animation/animation_blend_space_2d.h b/scene/animation/animation_blend_space_2d.h index 65d09a550d..a919fff1d2 100644 --- a/scene/animation/animation_blend_space_2d.h +++ b/scene/animation/animation_blend_space_2d.h @@ -126,7 +126,7 @@ public: void set_y_label(const String &p_label); String get_y_label() const; - virtual float process(float p_time, bool p_seek) override; + virtual double process(double p_time, bool p_seek) override; virtual String get_caption() const override; Vector2 get_closest_point(const Vector2 &p_point); diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index 6a988042b5..d11387902a 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -63,11 +63,11 @@ void AnimationNodeAnimation::_validate_property(PropertyInfo &property) const { } } -float AnimationNodeAnimation::process(float p_time, bool p_seek) { +double AnimationNodeAnimation::process(double p_time, bool p_seek) { AnimationPlayer *ap = state->player; ERR_FAIL_COND_V(!ap, 0); - float time = get_parameter(this->time); + double time = get_parameter(this->time); if (!ap->has_animation(animation)) { AnimationNodeBlendTree *tree = Object::cast_to<AnimationNodeBlendTree>(parent); @@ -84,7 +84,7 @@ float AnimationNodeAnimation::process(float p_time, bool p_seek) { Ref<Animation> anim = ap->get_animation(animation); - float step; + double step; if (p_seek) { time = p_time; @@ -94,7 +94,7 @@ float AnimationNodeAnimation::process(float p_time, bool p_seek) { step = p_time; } - float anim_size = anim->get_length(); + double anim_size = anim->get_length(); if (anim->has_loop()) { if (anim_size) { @@ -202,12 +202,12 @@ bool AnimationNodeOneShot::has_filter() const { return true; } -float AnimationNodeOneShot::process(float p_time, bool p_seek) { +double AnimationNodeOneShot::process(double p_time, bool p_seek) { bool active = get_parameter(this->active); bool prev_active = get_parameter(this->prev_active); - float time = get_parameter(this->time); - float remaining = get_parameter(this->remaining); - float time_to_restart = get_parameter(this->time_to_restart); + double time = get_parameter(this->time); + double remaining = get_parameter(this->remaining); + double time_to_restart = get_parameter(this->time_to_restart); if (!active) { //make it as if this node doesn't exist, pass input 0 by. @@ -370,9 +370,9 @@ bool AnimationNodeAdd2::has_filter() const { return true; } -float AnimationNodeAdd2::process(float p_time, bool p_seek) { - float amount = get_parameter(add_amount); - float rem0 = blend_input(0, p_time, p_seek, 1.0, FILTER_IGNORE, !sync); +double AnimationNodeAdd2::process(double p_time, bool p_seek) { + 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); return rem0; @@ -416,10 +416,10 @@ bool AnimationNodeAdd3::has_filter() const { return true; } -float AnimationNodeAdd3::process(float p_time, bool p_seek) { - float amount = get_parameter(add_amount); +double AnimationNodeAdd3::process(double p_time, bool p_seek) { + double amount = get_parameter(add_amount); blend_input(0, p_time, p_seek, MAX(0, -amount), FILTER_PASS, !sync); - float rem0 = blend_input(1, p_time, p_seek, 1.0, FILTER_IGNORE, !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); return rem0; @@ -452,11 +452,11 @@ String AnimationNodeBlend2::get_caption() const { return "Blend2"; } -float AnimationNodeBlend2::process(float p_time, bool p_seek) { - float amount = get_parameter(blend_amount); +double AnimationNodeBlend2::process(double p_time, bool p_seek) { + double amount = get_parameter(blend_amount); - float rem0 = blend_input(0, p_time, p_seek, 1.0 - amount, FILTER_BLEND, !sync); - float rem1 = blend_input(1, p_time, p_seek, amount, FILTER_PASS, !sync); + 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); return amount > 0.5 ? rem1 : rem0; //hacky but good enough } @@ -507,11 +507,11 @@ bool AnimationNodeBlend3::is_using_sync() const { return sync; } -float AnimationNodeBlend3::process(float p_time, bool p_seek) { - float amount = get_parameter(blend_amount); - float rem0 = blend_input(0, p_time, p_seek, MAX(0, -amount), FILTER_IGNORE, !sync); - float rem1 = blend_input(1, p_time, p_seek, 1.0 - ABS(amount), FILTER_IGNORE, !sync); - float rem2 = blend_input(2, p_time, p_seek, MAX(0, amount), FILTER_IGNORE, !sync); +double AnimationNodeBlend3::process(double p_time, bool p_seek) { + 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); return amount > 0.5 ? rem2 : (amount < -0.5 ? rem0 : rem1); //hacky but good enough } @@ -545,8 +545,8 @@ String AnimationNodeTimeScale::get_caption() const { return "TimeScale"; } -float AnimationNodeTimeScale::process(float p_time, bool p_seek) { - float scale = get_parameter(this->scale); +double AnimationNodeTimeScale::process(double p_time, bool p_seek) { + double scale = get_parameter(this->scale); if (p_seek) { return blend_input(0, p_time, true, 1.0, FILTER_IGNORE, false); } else { @@ -575,12 +575,12 @@ String AnimationNodeTimeSeek::get_caption() const { return "Seek"; } -float AnimationNodeTimeSeek::process(float p_time, bool p_seek) { - float seek_pos = get_parameter(this->seek_pos); +double AnimationNodeTimeSeek::process(double p_time, bool p_seek) { + double seek_pos = get_parameter(this->seek_pos); if (p_seek) { return blend_input(0, p_time, true, 1.0, FILTER_IGNORE, false); } else if (seek_pos >= 0) { - float ret = blend_input(0, seek_pos, true, 1.0, FILTER_IGNORE, false); + double ret = blend_input(0, seek_pos, true, 1.0, FILTER_IGNORE, false); set_parameter(this->seek_pos, -1.0); //reset return ret; } else { @@ -676,13 +676,13 @@ float AnimationNodeTransition::get_cross_fade_time() const { return xfade; } -float AnimationNodeTransition::process(float p_time, bool p_seek) { +double AnimationNodeTransition::process(double p_time, bool p_seek) { int current = get_parameter(this->current); int prev = get_parameter(this->prev); int prev_current = get_parameter(this->prev_current); - float time = get_parameter(this->time); - float prev_xfading = get_parameter(this->prev_xfading); + double time = get_parameter(this->time); + double prev_xfading = get_parameter(this->prev_xfading); bool switched = current != prev_current; @@ -794,7 +794,7 @@ String AnimationNodeOutput::get_caption() const { return "Output"; } -float AnimationNodeOutput::process(float p_time, bool p_seek) { +double AnimationNodeOutput::process(double p_time, bool p_seek) { return blend_input(0, p_time, p_seek, 1.0); } @@ -816,7 +816,7 @@ void AnimationNodeBlendTree::add_node(const StringName &p_name, Ref<AnimationNod nodes[p_name] = n; emit_changed(); - emit_signal("tree_changed"); + emit_signal(SNAME("tree_changed")); p_node->connect("tree_changed", callable_mp(this, &AnimationNodeBlendTree::_tree_changed), varray(), CONNECT_REFERENCE_COUNTED); p_node->connect("changed", callable_mp(this, &AnimationNodeBlendTree::_node_changed), varray(p_name), CONNECT_REFERENCE_COUNTED); @@ -896,7 +896,7 @@ void AnimationNodeBlendTree::remove_node(const StringName &p_name) { } emit_changed(); - emit_signal("tree_changed"); + emit_signal(SNAME("tree_changed")); } void AnimationNodeBlendTree::rename_node(const StringName &p_name, const StringName &p_new_name) { @@ -921,7 +921,7 @@ void AnimationNodeBlendTree::rename_node(const StringName &p_name, const StringN //connection must be done with new name nodes[p_new_name].node->connect("changed", callable_mp(this, &AnimationNodeBlendTree::_node_changed), varray(p_new_name), CONNECT_REFERENCE_COUNTED); - emit_signal("tree_changed"); + emit_signal(SNAME("tree_changed")); } void AnimationNodeBlendTree::connect_node(const StringName &p_input_node, int p_input_index, const StringName &p_output_node) { @@ -1007,7 +1007,7 @@ String AnimationNodeBlendTree::get_caption() const { return "BlendTree"; } -float AnimationNodeBlendTree::process(float p_time, bool p_seek) { +double AnimationNodeBlendTree::process(double p_time, bool p_seek) { 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); } @@ -1089,10 +1089,10 @@ bool AnimationNodeBlendTree::_get(const StringName &p_name, Variant &r_ret) cons conns.resize(nc.size() * 3); int idx = 0; - for (List<NodeConnection>::Element *E = nc.front(); E; E = E->next()) { - conns[idx * 3 + 0] = E->get().input_node; - conns[idx * 3 + 1] = E->get().input_index; - conns[idx * 3 + 2] = E->get().output_node; + for (const NodeConnection &E : nc) { + conns[idx * 3 + 0] = E.input_node; + conns[idx * 3 + 1] = E.input_index; + conns[idx * 3 + 2] = E.output_node; idx++; } @@ -1110,8 +1110,8 @@ void AnimationNodeBlendTree::_get_property_list(List<PropertyInfo> *p_list) cons } names.sort_custom<StringName::AlphCompare>(); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - String name = E->get(); + for (const StringName &E : names) { + String name = E; if (name != "output") { p_list->push_back(PropertyInfo(Variant::OBJECT, "nodes/" + name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NOEDITOR)); } @@ -1124,12 +1124,13 @@ void AnimationNodeBlendTree::_get_property_list(List<PropertyInfo> *p_list) cons void AnimationNodeBlendTree::reset_state() { graph_offset = Vector2(); nodes.clear(); + _initialize_node_tree(); emit_changed(); - emit_signal("tree_changed"); + emit_signal(SNAME("tree_changed")); } void AnimationNodeBlendTree::_tree_changed() { - emit_signal("tree_changed"); + emit_signal(SNAME("tree_changed")); } void AnimationNodeBlendTree::_node_changed(const StringName &p_node) { @@ -1162,7 +1163,7 @@ void AnimationNodeBlendTree::_bind_methods() { BIND_CONSTANT(CONNECTION_ERROR_CONNECTION_EXISTS); } -AnimationNodeBlendTree::AnimationNodeBlendTree() { +void AnimationNodeBlendTree::_initialize_node_tree() { Ref<AnimationNodeOutput> output; output.instantiate(); Node n; @@ -1172,5 +1173,9 @@ AnimationNodeBlendTree::AnimationNodeBlendTree() { nodes["output"] = n; } +AnimationNodeBlendTree::AnimationNodeBlendTree() { + _initialize_node_tree(); +} + AnimationNodeBlendTree::~AnimationNodeBlendTree() { } diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h index d82658c8c2..258443a999 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 float process(float p_time, bool p_seek) override; + virtual double process(double p_time, bool p_seek) override; void set_animation(const StringName &p_name); StringName get_animation() const; @@ -122,7 +122,7 @@ public: bool is_using_sync() const; virtual bool has_filter() const override; - virtual float process(float p_time, bool p_seek) override; + virtual double process(double p_time, bool p_seek) override; AnimationNodeOneShot(); }; @@ -148,7 +148,7 @@ public: bool is_using_sync() const; virtual bool has_filter() const override; - virtual float process(float p_time, bool p_seek) override; + virtual double process(double p_time, bool p_seek) override; AnimationNodeAdd2(); }; @@ -172,7 +172,7 @@ public: bool is_using_sync() const; virtual bool has_filter() const override; - virtual float process(float p_time, bool p_seek) override; + virtual double process(double p_time, bool p_seek) override; AnimationNodeAdd3(); }; @@ -191,7 +191,7 @@ public: virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; virtual String get_caption() const override; - virtual float process(float p_time, bool p_seek) override; + virtual double process(double p_time, bool p_seek) override; void set_use_sync(bool p_sync); bool is_using_sync() const; @@ -218,7 +218,7 @@ public: void set_use_sync(bool p_sync); bool is_using_sync() const; - float process(float p_time, bool p_seek) override; + double process(double p_time, bool p_seek) override; AnimationNodeBlend3(); }; @@ -236,7 +236,7 @@ public: virtual String get_caption() const override; - float process(float p_time, bool p_seek) override; + double process(double p_time, bool p_seek) override; AnimationNodeTimeScale(); }; @@ -255,7 +255,7 @@ public: virtual String get_caption() const override; - float process(float p_time, bool p_seek) override; + double process(double p_time, bool p_seek) override; AnimationNodeTimeSeek(); }; @@ -313,7 +313,7 @@ public: void set_cross_fade_time(float p_fade); float get_cross_fade_time() const; - float process(float p_time, bool p_seek) override; + double process(double p_time, bool p_seek) override; AnimationNodeTransition(); }; @@ -323,7 +323,7 @@ class AnimationNodeOutput : public AnimationNode { public: virtual String get_caption() const override; - virtual float process(float p_time, bool p_seek) override; + virtual double process(double p_time, bool p_seek) override; AnimationNodeOutput(); }; @@ -345,6 +345,8 @@ class AnimationNodeBlendTree : public AnimationRootNode { void _tree_changed(); void _node_changed(const StringName &p_node); + void _initialize_node_tree(); + protected: static void _bind_methods(); bool _set(const StringName &p_name, const Variant &p_value); @@ -390,7 +392,7 @@ public: void get_node_connections(List<NodeConnection> *r_connections) const; virtual String get_caption() const override; - virtual float process(float p_time, bool p_seek) override; + virtual double process(double p_time, bool p_seek) override; void get_node_list(List<StringName> *r_list); diff --git a/scene/animation/animation_cache.cpp b/scene/animation/animation_cache.cpp index b8980fd56b..56743007e4 100644 --- a/scene/animation/animation_cache.cpp +++ b/scene/animation/animation_cache.cpp @@ -252,8 +252,8 @@ void AnimationCache::set_all(float p_time, float p_delta) { List<int> indices; animation->value_track_get_key_indices(i, p_time, p_delta, &indices); - for (List<int>::Element *E = indices.front(); E; E = E->next()) { - Variant v = animation->track_get_key_value(i, E->get()); + for (int &E : indices) { + Variant v = animation->track_get_key_value(i, E); set_track_value(i, v); } } @@ -263,9 +263,9 @@ void AnimationCache::set_all(float p_time, float p_delta) { List<int> indices; animation->method_track_get_key_indices(i, p_time, p_delta, &indices); - for (List<int>::Element *E = indices.front(); E; E = E->next()) { - Vector<Variant> args = animation->method_track_get_params(i, E->get()); - StringName name = animation->method_track_get_name(i, E->get()); + for (int &E : indices) { + Vector<Variant> args = animation->method_track_get_params(i, E); + StringName name = animation->method_track_get_name(i, E); Callable::CallError err; if (!args.size()) { diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index f494f5c163..9fc1dbd0c6 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -57,7 +57,7 @@ void AnimationNodeStateMachineTransition::set_advance_condition(const StringName } else { advance_condition_name = StringName(); } - emit_signal("advance_condition_changed"); + emit_signal(SNAME("advance_condition_changed")); } StringName AnimationNodeStateMachineTransition::get_advance_condition() const { @@ -286,7 +286,7 @@ bool AnimationNodeStateMachinePlayback::_travel(AnimationNodeStateMachine *p_sta return true; } -float AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_state_machine, float p_time, bool p_seek) { +double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek) { //if not playing and it can restart, then restart if (!playing && start_request == StringName()) { if (!stop_request && p_state_machine->start_node) { @@ -512,8 +512,8 @@ void AnimationNodeStateMachine::get_parameter_list(List<PropertyInfo> *r_list) c } advance_conditions.sort_custom<StringName::AlphCompare>(); - for (List<StringName>::Element *E = advance_conditions.front(); E; E = E->next()) { - r_list->push_back(PropertyInfo(Variant::BOOL, E->get())); + for (const StringName &E : advance_conditions) { + r_list->push_back(PropertyInfo(Variant::BOOL, E)); } } @@ -539,7 +539,7 @@ void AnimationNodeStateMachine::add_node(const StringName &p_name, Ref<Animation states[p_name] = state; emit_changed(); - emit_signal("tree_changed"); + emit_signal(SNAME("tree_changed")); p_node->connect("tree_changed", callable_mp(this, &AnimationNodeStateMachine::_tree_changed), varray(), CONNECT_REFERENCE_COUNTED); } @@ -559,7 +559,7 @@ void AnimationNodeStateMachine::replace_node(const StringName &p_name, Ref<Anima states[p_name].node = p_node; emit_changed(); - emit_signal("tree_changed"); + emit_signal(SNAME("tree_changed")); p_node->connect("tree_changed", callable_mp(this, &AnimationNodeStateMachine::_tree_changed), varray(), CONNECT_REFERENCE_COUNTED); } @@ -636,7 +636,7 @@ void AnimationNodeStateMachine::remove_node(const StringName &p_name) { }*/ emit_changed(); - emit_signal("tree_changed"); + emit_signal(SNAME("tree_changed")); } void AnimationNodeStateMachine::rename_node(const StringName &p_name, const StringName &p_new_name) { @@ -669,7 +669,7 @@ void AnimationNodeStateMachine::rename_node(const StringName &p_name, const Stri }*/ //path.clear(); //clear path - emit_signal("tree_changed"); + emit_signal(SNAME("tree_changed")); } void AnimationNodeStateMachine::get_node_list(List<StringName> *r_nodes) const { @@ -679,8 +679,8 @@ void AnimationNodeStateMachine::get_node_list(List<StringName> *r_nodes) const { } nodes.sort_custom<StringName::AlphCompare>(); - for (List<StringName>::Element *E = nodes.front(); E; E = E->next()) { - r_nodes->push_back(E->get()); + for (const StringName &E : nodes) { + r_nodes->push_back(E); } } @@ -790,7 +790,7 @@ Vector2 AnimationNodeStateMachine::get_graph_offset() const { return graph_offset; } -float AnimationNodeStateMachine::process(float p_time, bool p_seek) { +double AnimationNodeStateMachine::process(double p_time, bool p_seek) { Ref<AnimationNodeStateMachinePlayback> playback = get_parameter(this->playback); ERR_FAIL_COND_V(playback.is_null(), 0.0); @@ -902,8 +902,7 @@ void AnimationNodeStateMachine::_get_property_list(List<PropertyInfo> *p_list) c } names.sort_custom<StringName::AlphCompare>(); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - String name = E->get(); + for (const StringName &name : names) { p_list->push_back(PropertyInfo(Variant::OBJECT, "states/" + name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NOEDITOR)); p_list->push_back(PropertyInfo(Variant::VECTOR2, "states/" + name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); } @@ -923,7 +922,7 @@ void AnimationNodeStateMachine::reset_state() { graph_offset = Vector2(); emit_changed(); - emit_signal("tree_changed"); + emit_signal(SNAME("tree_changed")); } void AnimationNodeStateMachine::set_node_position(const StringName &p_name, const Vector2 &p_position) { @@ -937,7 +936,7 @@ Vector2 AnimationNodeStateMachine::get_node_position(const StringName &p_name) c } void AnimationNodeStateMachine::_tree_changed() { - emit_signal("tree_changed"); + emit_signal(SNAME("tree_changed")); } void AnimationNodeStateMachine::_bind_methods() { diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h index 9c1bca63c3..6f0e3107fd 100644 --- a/scene/animation/animation_node_state_machine.h +++ b/scene/animation/animation_node_state_machine.h @@ -114,7 +114,7 @@ class AnimationNodeStateMachinePlayback : public Resource { bool _travel(AnimationNodeStateMachine *p_state_machine, const StringName &p_travel); - float process(AnimationNodeStateMachine *p_state_machine, float p_time, bool p_seek); + double process(AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek); protected: static void _bind_methods(); @@ -210,7 +210,7 @@ public: void set_graph_offset(const Vector2 &p_offset); Vector2 get_graph_offset() const; - virtual float process(float p_time, bool p_seek) override; + virtual double process(double p_time, bool p_seek) 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 6154eef3cf..5825a35030 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -176,8 +176,8 @@ void AnimationPlayer::_get_property_list(List<PropertyInfo> *p_list) const { anim_names.sort(); - for (List<PropertyInfo>::Element *E = anim_names.front(); E; E = E->next()) { - p_list->push_back(E->get()); + for (const PropertyInfo &E : anim_names) { + p_list->push_back(E); } p_list->push_back(PropertyInfo(Variant::ARRAY, "blend_times", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); @@ -341,7 +341,7 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_ov } } -void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float p_time, float p_delta, float p_interp, bool p_is_current, bool p_seeked, bool p_started) { +void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double p_time, double p_delta, float p_interp, bool p_is_current, bool p_seeked, bool p_started) { _ensure_node_caches(p_anim); ERR_FAIL_COND(p_anim->node_cache.size() != p_anim->animation->get_track_count()); @@ -485,8 +485,8 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float List<int> indices; a->value_track_get_key_indices(i, p_time, p_delta, &indices); - for (List<int>::Element *F = indices.front(); F; F = F->next()) { - Variant value = a->track_get_key_value(i, F->get()); + for (int &F : indices) { + Variant value = a->track_get_key_value(i, F); switch (pa->special) { case SP_NONE: { bool valid; @@ -544,9 +544,9 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float a->method_track_get_key_indices(i, p_time, p_delta, &indices); - for (List<int>::Element *E = indices.front(); E; E = E->next()) { - StringName method = a->method_track_get_name(i, E->get()); - Vector<Variant> params = a->method_track_get_params(i, E->get()); + for (int &E : indices) { + StringName method = a->method_track_get_name(i, E); + Vector<Variant> params = a->method_track_get_params(i, E); int s = params.size(); @@ -557,6 +557,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float } #endif + static_assert(VARIANT_ARG_MAX == 8, "This code needs to be updated if VARIANT_ARG_MAX != 8"); if (can_call) { if (method_call_mode == ANIMATION_METHOD_CALL_DEFERRED) { MessageQueue::get_singleton()->push_call( @@ -566,7 +567,10 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float s >= 2 ? params[1] : Variant(), s >= 3 ? params[2] : Variant(), s >= 4 ? params[3] : Variant(), - s >= 5 ? params[4] : Variant()); + s >= 5 ? params[4] : Variant(), + s >= 6 ? params[5] : Variant(), + s >= 7 ? params[6] : Variant(), + s >= 8 ? params[7] : Variant()); } else { nc->node->call( method, @@ -574,7 +578,10 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float s >= 2 ? params[1] : Variant(), s >= 3 ? params[2] : Variant(), s >= 4 ? params[3] : Variant(), - s >= 5 ? params[4] : Variant()); + s >= 5 ? params[4] : Variant(), + s >= 6 ? params[5] : Variant(), + s >= 7 ? params[6] : Variant(), + s >= 8 ? params[7] : Variant()); } } } @@ -716,7 +723,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float continue; } - float pos = a->track_get_key_time(i, idx); + double pos = a->track_get_key_time(i, idx); StringName anim_name = a->animation_track_get_key_animation(i, idx); if (String(anim_name) == "[stop]" || !player->has_animation(anim_name)) { @@ -725,12 +732,12 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float Ref<Animation> anim = player->get_animation(anim_name); - float at_anim_pos; + double at_anim_pos; if (anim->has_loop()) { - at_anim_pos = Math::fposmod(p_time - pos, anim->get_length()); //seek to loop + at_anim_pos = Math::fposmod(p_time - pos, (double)anim->get_length()); //seek to loop } else { - at_anim_pos = MAX(anim->get_length(), p_time - pos); //seek to end + at_anim_pos = MIN((double)anim->get_length(), p_time - pos); //seek to end } if (player->is_playing() || p_seeked) { @@ -758,6 +765,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float } } else { player->play(anim_name); + player->seek(0.0, true); nc->animation_playing = true; playing_caches.insert(nc); } @@ -769,11 +777,11 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float } } -void AnimationPlayer::_animation_process_data(PlaybackData &cd, float p_delta, float p_blend, bool p_seeked, bool p_started) { - float delta = p_delta * speed_scale * cd.speed_scale; - float next_pos = cd.pos + delta; +void AnimationPlayer::_animation_process_data(PlaybackData &cd, double p_delta, float p_blend, bool p_seeked, bool p_started) { + double delta = p_delta * speed_scale * cd.speed_scale; + double next_pos = cd.pos + delta; - float len = cd.from->animation->get_length(); + real_t len = cd.from->animation->get_length(); bool loop = cd.from->animation->has_loop(); if (!loop) { @@ -801,7 +809,7 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, float p_delta, f } } else { - float looped_next_pos = Math::fposmod(next_pos, len); + double looped_next_pos = Math::fposmod(next_pos, (double)len); if (looped_next_pos == 0 && next_pos != 0) { // Loop multiples of the length to it, rather than 0 // so state at time=length is previewable in the editor @@ -816,7 +824,7 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, float p_delta, f _animation_process_animation(cd.from, cd.pos, delta, p_blend, &cd == &playback.current, p_seeked, p_started); } -void AnimationPlayer::_animation_process2(float p_delta, bool p_started) { +void AnimationPlayer::_animation_process2(double p_delta, bool p_started) { Playback &c = playback; accum_pass++; @@ -920,7 +928,7 @@ void AnimationPlayer::_animation_update_transforms() { cache_update_bezier_size = 0; } -void AnimationPlayer::_animation_process(float p_delta) { +void AnimationPlayer::_animation_process(double p_delta) { if (playback.current.from) { end_reached = false; end_notify = false; @@ -1069,8 +1077,8 @@ void AnimationPlayer::get_animation_list(List<StringName> *p_animations) const { anims.sort(); - for (List<String>::Element *E = anims.front(); E; E = E->next()) { - p_animations->push_back(E->get()); + for (const String &E : anims) { + p_animations->push_back(E); } } @@ -1111,8 +1119,8 @@ void AnimationPlayer::queue(const StringName &p_name) { Vector<String> AnimationPlayer::get_queue() { Vector<String> ret; - for (List<StringName>::Element *E = queued.front(); E; E = E->next()) { - ret.push_back(E->get()); + for (const StringName &E : queued) { + ret.push_back(E); } return ret; @@ -1276,7 +1284,7 @@ float AnimationPlayer::get_playing_speed() const { return speed_scale * playback.current.speed_scale; } -void AnimationPlayer::seek(float p_time, bool p_update) { +void AnimationPlayer::seek(double p_time, bool p_update) { if (!playback.current.from) { if (playback.assigned) { ERR_FAIL_COND(!animation_set.has(playback.assigned)); @@ -1292,7 +1300,7 @@ void AnimationPlayer::seek(float p_time, bool p_update) { } } -void AnimationPlayer::seek_delta(float p_time, float p_delta) { +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)); @@ -1325,7 +1333,7 @@ float AnimationPlayer::get_current_animation_length() const { void AnimationPlayer::_animation_changed() { clear_caches(); - emit_signal("caches_cleared"); + emit_signal(SNAME("caches_cleared")); if (is_playing()) { playback.seeked = true; //need to restart stuff, like audio } @@ -1486,7 +1494,7 @@ NodePath AnimationPlayer::get_root() const { void AnimationPlayer::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { #ifdef TOOLS_ENABLED - const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", 0) ? "'" : "\""; + const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\""; #else const String quote_style = "\""; #endif @@ -1495,8 +1503,8 @@ void AnimationPlayer::get_argument_options(const StringName &p_function, int p_i if (p_idx == 0 && (p_function == "play" || p_function == "play_backwards" || p_function == "remove_animation" || p_function == "has_animation" || p_function == "queue")) { List<StringName> al; get_animation_list(&al); - for (List<StringName>::Element *E = al.front(); E; E = E->next()) { - r_options->push_back(quote_style + String(E->get()) + quote_style); + for (const StringName &name : al) { + r_options->push_back(String(name).quote(quote_style)); } } Node::get_argument_options(p_function, p_idx, r_options); diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h index 7cd9de1fa1..b693e29bdf 100644 --- a/scene/animation/animation_player.h +++ b/scene/animation/animation_player.h @@ -215,13 +215,13 @@ private: NodePath root; - void _animation_process_animation(AnimationData *p_anim, float p_time, float p_delta, float p_interp, bool p_is_current = true, bool p_seeked = false, bool p_started = false); + void _animation_process_animation(AnimationData *p_anim, double p_time, double p_delta, float p_interp, bool p_is_current = true, bool p_seeked = false, bool p_started = false); void _ensure_node_caches(AnimationData *p_anim, Node *p_root_override = nullptr); - void _animation_process_data(PlaybackData &cd, float p_delta, float p_blend, bool p_seeked, bool p_started); - void _animation_process2(float p_delta, bool p_started); + void _animation_process_data(PlaybackData &cd, double p_delta, float p_blend, bool p_seeked, bool p_started); + void _animation_process2(double p_delta, bool p_started); void _animation_update_transforms(); - void _animation_process(float p_delta); + void _animation_process(double p_delta); void _node_removed(Node *p_node); void _stop_playing_caches(); @@ -306,8 +306,8 @@ public: void set_method_call_mode(AnimationMethodCallMode p_mode); AnimationMethodCallMode get_method_call_mode() const; - void seek(float p_time, bool p_update = false); - void seek_delta(float p_time, float p_delta); + void seek(double p_time, bool p_update = false); + void seek_delta(double p_time, float p_delta); float get_current_animation_position() const; float get_current_animation_length() const; diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index 1e07f83d09..88fb960164 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -36,8 +36,9 @@ #include "servers/audio/audio_stream.h" void AnimationNode::get_parameter_list(List<PropertyInfo> *r_list) const { - if (get_script_instance()) { - Array parameters = get_script_instance()->call("_get_parameter_list"); + Array parameters; + + if (GDVIRTUAL_CALL(_get_parameter_list, parameters)) { for (int i = 0; i < parameters.size(); i++) { Dictionary d = parameters[i]; ERR_CONTINUE(d.is_empty()); @@ -47,8 +48,9 @@ void AnimationNode::get_parameter_list(List<PropertyInfo> *r_list) const { } Variant AnimationNode::get_parameter_default_value(const StringName &p_parameter) const { - if (get_script_instance()) { - return get_script_instance()->call("_get_parameter_default_value", p_parameter); + Variant ret; + if (GDVIRTUAL_CALL(_get_parameter_default_value, p_parameter, ret)) { + return ret; } return Variant(); } @@ -72,20 +74,20 @@ Variant AnimationNode::get_parameter(const StringName &p_name) const { } void AnimationNode::get_child_nodes(List<ChildNode> *r_child_nodes) { - if (get_script_instance()) { - Dictionary cn = get_script_instance()->call("_get_child_nodes"); + Dictionary cn; + if (GDVIRTUAL_CALL(_get_child_nodes, cn)) { List<Variant> keys; cn.get_key_list(&keys); - for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { + for (const Variant &E : keys) { ChildNode child; - child.name = E->get(); - child.node = cn[E->get()]; + child.name = E; + child.node = cn[E]; r_child_nodes->push_back(child); } } } -void AnimationNode::blend_animation(const StringName &p_animation, float p_time, float p_delta, bool p_seeked, float p_blend) { +void AnimationNode::blend_animation(const StringName &p_animation, real_t p_time, real_t p_delta, bool p_seeked, real_t p_blend) { ERR_FAIL_COND(!state); ERR_FAIL_COND(!state->player->has_animation(p_animation)); @@ -115,13 +117,13 @@ void AnimationNode::blend_animation(const StringName &p_animation, float p_time, state->animation_states.push_back(anim_state); } -float AnimationNode::_pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, float p_time, bool p_seek, const Vector<StringName> &p_connections) { +real_t AnimationNode::_pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, real_t p_time, bool p_seek, const Vector<StringName> &p_connections) { base_path = p_base_path; parent = p_parent; connections = p_connections; state = p_state; - float t = process(p_time, p_seek); + real_t t = process(p_time, p_seek); state = nullptr; parent = nullptr; @@ -137,10 +139,10 @@ void AnimationNode::make_invalid(const String &p_reason) { if (state->invalid_reasons != String()) { state->invalid_reasons += "\n"; } - state->invalid_reasons += "- " + p_reason; + state->invalid_reasons += String::utf8("• ") + p_reason; } -float AnimationNode::blend_input(int p_input, float p_time, bool p_seek, float p_blend, FilterAction p_filter, bool p_optimize) { +real_t AnimationNode::blend_input(int p_input, real_t p_time, bool p_seek, real_t p_blend, FilterAction p_filter, bool p_optimize) { ERR_FAIL_INDEX_V(p_input, inputs.size(), 0); ERR_FAIL_COND_V(!state, 0); @@ -158,8 +160,8 @@ float AnimationNode::blend_input(int p_input, float p_time, bool p_seek, float p Ref<AnimationNode> node = blend_tree->get_node(node_name); //inputs.write[p_input].last_pass = state->last_pass; - float activity = 0.0; - float 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); + real_t activity = 0.0; + real_t 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); Vector<AnimationTree::Activity> *activity_ptr = state->tree->input_activity_map.getptr(base_path); @@ -170,11 +172,11 @@ float AnimationNode::blend_input(int p_input, float p_time, bool p_seek, float p return ret; } -float AnimationNode::blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter, bool p_optimize) { +real_t AnimationNode::blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, real_t p_time, bool p_seek, real_t p_blend, FilterAction p_filter, bool p_optimize) { return _blend_node(p_sub_path, Vector<StringName>(), this, p_node, p_time, p_seek, p_blend, p_filter, p_optimize); } -float AnimationNode::_blend_node(const StringName &p_subpath, const Vector<StringName> &p_connections, AnimationNode *p_new_parent, Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter, bool p_optimize, float *r_max) { +real_t AnimationNode::_blend_node(const StringName &p_subpath, const Vector<StringName> &p_connections, AnimationNode *p_new_parent, Ref<AnimationNode> p_node, real_t p_time, bool p_seek, real_t p_blend, FilterAction p_filter, bool p_optimize, real_t *r_max) { ERR_FAIL_COND_V(!p_node.is_valid(), 0); ERR_FAIL_COND_V(!state, 0); @@ -184,8 +186,8 @@ float AnimationNode::_blend_node(const StringName &p_subpath, const Vector<Strin p_node->blends.resize(blend_count); } - float *blendw = p_node->blends.ptrw(); - const float *blendr = blends.ptr(); + real_t *blendw = p_node->blends.ptrw(); + const real_t *blendr = blends.ptr(); bool any_valid = false; @@ -298,8 +300,9 @@ String AnimationNode::get_input_name(int p_input) { } String AnimationNode::get_caption() const { - if (get_script_instance()) { - return get_script_instance()->call("_get_caption"); + String ret; + if (GDVIRTUAL_CALL(_get_caption, ret)) { + return ret; } return "Node"; @@ -328,9 +331,10 @@ void AnimationNode::remove_input(int p_index) { emit_changed(); } -float AnimationNode::process(float p_time, bool p_seek) { - if (get_script_instance()) { - return get_script_instance()->call("_process", p_time, p_seek); +double AnimationNode::process(double p_time, bool p_seek) { + double ret; + if (GDVIRTUAL_CALL(_process, p_time, p_seek, ret)) { + return ret; } return 0; @@ -357,8 +361,9 @@ bool AnimationNode::is_path_filtered(const NodePath &p_path) const { } bool AnimationNode::has_filter() const { - if (get_script_instance()) { - return get_script_instance()->call("_has_filter"); + bool ret; + if (GDVIRTUAL_CALL(_has_filter, ret)) { + return ret; } return false; @@ -390,8 +395,9 @@ void AnimationNode::_validate_property(PropertyInfo &property) const { } Ref<AnimationNode> AnimationNode::get_child_by_name(const StringName &p_name) { - if (get_script_instance()) { - return get_script_instance()->call("_get_child_by_name", p_name); + Ref<AnimationNode> ret; + if (GDVIRTUAL_CALL(_get_child_by_name, p_name, ret)) { + return ret; } return Ref<AnimationNode>(); } @@ -422,17 +428,13 @@ void AnimationNode::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter_enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_filter_enabled", "is_filter_enabled"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "filters", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_filters", "_get_filters"); - BIND_VMETHOD(MethodInfo(Variant::DICTIONARY, "_get_child_nodes")); - BIND_VMETHOD(MethodInfo(Variant::ARRAY, "_get_parameter_list")); - BIND_VMETHOD(MethodInfo(Variant::OBJECT, "_get_child_by_name", PropertyInfo(Variant::STRING, "name"))); - { - MethodInfo mi = MethodInfo(Variant::NIL, "_get_parameter_default_value", PropertyInfo(Variant::STRING_NAME, "name")); - mi.return_val.usage = PROPERTY_USAGE_NIL_IS_VARIANT; - BIND_VMETHOD(mi); - } - BIND_VMETHOD(MethodInfo("_process", PropertyInfo(Variant::FLOAT, "time"), PropertyInfo(Variant::BOOL, "seek"))); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_caption")); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_has_filter")); + GDVIRTUAL_BIND(_get_child_nodes); + 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(_get_caption); + GDVIRTUAL_BIND(_has_filter); ADD_SIGNAL(MethodInfo("removed_from_graph")); @@ -536,8 +538,8 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { List<StringName> sname; player->get_animation_list(&sname); - for (List<StringName>::Element *E = sname.front(); E; E = E->next()) { - Ref<Animation> anim = player->get_animation(E->get()); + for (const StringName &E : sname) { + Ref<Animation> anim = player->get_animation(E); for (int i = 0; i < anim->get_track_count(); i++) { NodePath path = anim->track_get_path(i); Animation::TrackType track_type = anim->track_get_type(i); @@ -561,7 +563,7 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { Node *child = parent->get_node_and_resource(path, resource, leftover_path); if (!child) { - ERR_PRINT("AnimationTree: '" + String(E->get()) + "', couldn't resolve track: '" + String(path) + "'"); + ERR_PRINT("AnimationTree: '" + String(E) + "', couldn't resolve track: '" + String(path) + "'"); continue; } @@ -590,7 +592,7 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { Node3D *node_3d = Object::cast_to<Node3D>(child); if (!node_3d) { - ERR_PRINT("AnimationTree: '" + String(E->get()) + "', transform track does not point to Node3D: '" + String(path) + "'"); + ERR_PRINT("AnimationTree: '" + String(E) + "', transform track does not point to Node3D: '" + String(path) + "'"); continue; } @@ -718,7 +720,7 @@ void AnimationTree::_clear_caches() { cache_valid = false; } -void AnimationTree::_process_graph(float p_delta) { +void AnimationTree::_process_graph(real_t p_delta) { _update_properties(); //if properties need updating, update them //check all tracks, see if they need modification @@ -790,7 +792,7 @@ void AnimationTree::_process_graph(float p_delta) { // root source blends root->blends.resize(state.track_count); - float *src_blendsw = root->blends.ptrw(); + real_t *src_blendsw = root->blends.ptrw(); for (int i = 0; i < state.track_count; i++) { src_blendsw[i] = 1.0; //by default all go to 1 for the root input } @@ -816,13 +818,11 @@ void AnimationTree::_process_graph(float p_delta) { { bool can_call = is_inside_tree() && !Engine::get_singleton()->is_editor_hint(); - for (List<AnimationNode::AnimationState>::Element *E = state.animation_states.front(); E; E = E->next()) { - const AnimationNode::AnimationState &as = E->get(); - + for (const AnimationNode::AnimationState &as : state.animation_states) { Ref<Animation> a = as.animation; - float time = as.time; - float delta = as.delta; - float weight = as.blend; + double time = as.time; + double delta = as.delta; + real_t weight = as.blend; bool seeked = as.seeked; for (int i = 0; i < a->get_track_count(); i++) { @@ -842,7 +842,7 @@ void AnimationTree::_process_graph(float p_delta) { ERR_CONTINUE(blend_idx < 0 || blend_idx >= state.track_count); - float blend = (*as.track_blends)[blend_idx] * weight; + real_t blend = (*as.track_blends)[blend_idx] * weight; if (blend < CMP_EPSILON) { continue; //nothing to blend @@ -862,7 +862,7 @@ void AnimationTree::_process_graph(float p_delta) { t->scale = Vector3(1, 1, 1); } - float prev_time = time - delta; + real_t prev_time = time - delta; if (prev_time < 0) { if (!a->has_loop()) { prev_time = 0; @@ -930,7 +930,7 @@ void AnimationTree::_process_graph(float p_delta) { t->rot = rot; t->rot_blend_accum = blend; } else { - float rot_total = t->rot_blend_accum + blend; + real_t rot_total = t->rot_blend_accum + blend; t->rot = rot.slerp(t->rot, t->rot_blend_accum / rot_total).normalized(); t->rot_blend_accum = rot_total; } @@ -962,8 +962,8 @@ void AnimationTree::_process_graph(float p_delta) { List<int> indices; a->value_track_get_key_indices(i, time, delta, &indices); - for (List<int>::Element *F = indices.front(); F; F = F->next()) { - Variant value = a->track_get_key_value(i, F->get()); + for (int &F : indices) { + Variant value = a->track_get_key_value(i, F); t->object->set_indexed(t->subpath, value); } } @@ -979,12 +979,13 @@ void AnimationTree::_process_graph(float p_delta) { a->method_track_get_key_indices(i, time, delta, &indices); - for (List<int>::Element *F = indices.front(); F; F = F->next()) { - StringName method = a->method_track_get_name(i, F->get()); - Vector<Variant> params = a->method_track_get_params(i, F->get()); + for (int &F : indices) { + StringName method = a->method_track_get_name(i, F); + Vector<Variant> params = a->method_track_get_params(i, F); int s = params.size(); + static_assert(VARIANT_ARG_MAX == 8, "This code needs to be updated if VARIANT_ARG_MAX != 8"); ERR_CONTINUE(s > VARIANT_ARG_MAX); if (can_call) { t->object->call_deferred( @@ -993,7 +994,10 @@ void AnimationTree::_process_graph(float p_delta) { s >= 2 ? params[1] : Variant(), s >= 3 ? params[2] : Variant(), s >= 4 ? params[3] : Variant(), - s >= 5 ? params[4] : Variant()); + s >= 5 ? params[4] : Variant(), + s >= 6 ? params[5] : Variant(), + s >= 7 ? params[6] : Variant(), + s >= 8 ? params[7] : Variant()); } } @@ -1001,7 +1005,7 @@ void AnimationTree::_process_graph(float p_delta) { case Animation::TYPE_BEZIER: { TrackCacheBezier *t = static_cast<TrackCacheBezier *>(track); - float bezier = a->bezier_track_interpolate(i, time); + real_t bezier = a->bezier_track_interpolate(i, time); if (t->process_pass != process_pass) { t->value = bezier; @@ -1027,10 +1031,10 @@ void AnimationTree::_process_graph(float p_delta) { t->playing = false; playing_caches.erase(t); } else { - float start_ofs = a->audio_track_get_key_start_offset(i, idx); + real_t start_ofs = a->audio_track_get_key_start_offset(i, idx); start_ofs += time - a->track_get_key_time(i, idx); - float end_ofs = a->audio_track_get_key_end_offset(i, idx); - float len = stream->get_length(); + real_t end_ofs = a->audio_track_get_key_end_offset(i, idx); + real_t len = stream->get_length(); if (start_ofs > len - end_ofs) { t->object->call("stop"); @@ -1066,9 +1070,9 @@ void AnimationTree::_process_graph(float p_delta) { t->playing = false; playing_caches.erase(t); } else { - float start_ofs = a->audio_track_get_key_start_offset(i, idx); - float end_ofs = a->audio_track_get_key_end_offset(i, idx); - float len = stream->get_length(); + real_t start_ofs = a->audio_track_get_key_start_offset(i, idx); + real_t end_ofs = a->audio_track_get_key_end_offset(i, idx); + real_t len = stream->get_length(); t->object->call("set_stream", stream); t->object->call("play", start_ofs); @@ -1091,7 +1095,7 @@ void AnimationTree::_process_graph(float p_delta) { if (!loop && time < t->start) { stop = true; } else if (t->len > 0) { - float len = t->start > time ? (a->get_length() - t->start) + time : time - t->start; + real_t len = t->start > time ? (a->get_length() - t->start) + time : time - t->start; if (len > t->len) { stop = true; @@ -1107,7 +1111,7 @@ void AnimationTree::_process_graph(float p_delta) { } } - float db = Math::linear2db(MAX(blend, 0.00001)); + real_t db = Math::linear2db(MAX(blend, 0.00001)); if (t->object->has_method("set_unit_db")) { t->object->call("set_unit_db", db); } else { @@ -1130,7 +1134,7 @@ void AnimationTree::_process_graph(float p_delta) { continue; } - float pos = a->track_get_key_time(i, idx); + double pos = a->track_get_key_time(i, idx); StringName anim_name = a->animation_track_get_key_animation(i, idx); if (String(anim_name) == "[stop]" || !player2->has_animation(anim_name)) { @@ -1139,10 +1143,10 @@ void AnimationTree::_process_graph(float p_delta) { Ref<Animation> anim = player2->get_animation(anim_name); - float at_anim_pos; + real_t at_anim_pos; if (anim->has_loop()) { - at_anim_pos = Math::fposmod(time - pos, anim->get_length()); //seek to loop + at_anim_pos = Math::fposmod(time - pos, (double)anim->get_length()); //seek to loop } else { at_anim_pos = MAX(anim->get_length(), time - pos); //seek to end } @@ -1236,7 +1240,7 @@ void AnimationTree::_process_graph(float p_delta) { } } -void AnimationTree::advance(float p_time) { +void AnimationTree::advance(real_t p_time) { _process_graph(p_time); } @@ -1327,7 +1331,7 @@ void AnimationTree::_tree_changed() { return; } - call_deferred("_update_properties"); + call_deferred(SNAME("_update_properties")); properties_dirty = true; } @@ -1351,9 +1355,7 @@ void AnimationTree::_update_properties_for_node(const String &p_base_path, Ref<A List<PropertyInfo> plist; node->get_parameter_list(&plist); - for (List<PropertyInfo>::Element *E = plist.front(); E; E = E->next()) { - PropertyInfo pinfo = E->get(); - + for (PropertyInfo &pinfo : plist) { StringName key = pinfo.name; if (!property_map.has(p_base_path + key)) { @@ -1369,8 +1371,8 @@ void AnimationTree::_update_properties_for_node(const String &p_base_path, Ref<A List<AnimationNode::ChildNode> children; node->get_child_nodes(&children); - for (List<AnimationNode::ChildNode>::Element *E = children.front(); E; E = E->next()) { - _update_properties_for_node(p_base_path + E->get().name + "/", E->get().node); + for (const AnimationNode::ChildNode &E : children) { + _update_properties_for_node(p_base_path + E.name + "/", E.node); } } @@ -1424,17 +1426,17 @@ void AnimationTree::_get_property_list(List<PropertyInfo> *p_list) const { const_cast<AnimationTree *>(this)->_update_properties(); } - for (const List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { - p_list->push_back(E->get()); + for (const PropertyInfo &E : properties) { + p_list->push_back(E); } } void AnimationTree::rename_parameter(const String &p_base, const String &p_new_base) { //rename values first - for (const List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { - if (E->get().name.begins_with(p_base)) { - String new_name = E->get().name.replace_first(p_base, p_new_base); - property_map[new_name] = property_map[E->get().name]; + for (const PropertyInfo &E : properties) { + if (E.name.begins_with(p_base)) { + String new_name = E.name.replace_first(p_base, p_new_base); + property_map[new_name] = property_map[E.name]; } } @@ -1443,7 +1445,7 @@ void AnimationTree::rename_parameter(const String &p_base, const String &p_new_b _update_properties(); } -float AnimationTree::get_connection_activity(const StringName &p_path, int p_connection) const { +real_t AnimationTree::get_connection_activity(const StringName &p_path, int p_connection) const { if (!input_activity_map_get.has(p_path)) { return 0; } diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index 60e0c7200a..1e0267682e 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -57,16 +57,16 @@ public: Vector<Input> inputs; - float process_input(int p_input, float p_time, bool p_seek, float p_blend); + real_t process_input(int p_input, real_t p_time, bool p_seek, real_t p_blend); friend class AnimationTree; struct AnimationState { Ref<Animation> animation; - float time = 0.0; - float delta = 0.0; - const Vector<float> *track_blends = nullptr; - float blend = 0.0; + double time = 0.0; + double delta = 0.0; + const Vector<real_t> *track_blends = nullptr; + real_t blend = 0.0; bool seeked = false; }; @@ -81,10 +81,10 @@ public: uint64_t last_pass = 0; }; - Vector<float> blends; + Vector<real_t> blends; State *state = nullptr; - float _pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, float p_time, bool p_seek, const Vector<StringName> &p_connections); + real_t _pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, real_t p_time, bool p_seek, const Vector<StringName> &p_connections); void _pre_update_animations(HashMap<NodePath, int> *track_map); //all this is temporary @@ -98,12 +98,12 @@ public: Array _get_filters() const; void _set_filters(const Array &p_filters); friend class AnimationNodeBlendTree; - float _blend_node(const StringName &p_subpath, const Vector<StringName> &p_connections, AnimationNode *p_new_parent, Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true, float *r_max = nullptr); + real_t _blend_node(const StringName &p_subpath, const Vector<StringName> &p_connections, AnimationNode *p_new_parent, Ref<AnimationNode> p_node, real_t p_time, bool p_seek, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true, real_t *r_max = nullptr); protected: - void blend_animation(const StringName &p_animation, float p_time, float p_delta, bool p_seeked, float p_blend); - float blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true); - float blend_input(int p_input, float p_time, bool p_seek, float p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true); + void blend_animation(const StringName &p_animation, real_t p_time, real_t p_delta, bool p_seeked, real_t p_blend); + real_t blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, real_t p_time, bool p_seek, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true); + real_t blend_input(int p_input, real_t p_time, bool p_seek, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true); void make_invalid(const String &p_reason); static void _bind_methods(); @@ -112,6 +112,14 @@ protected: void _set_parent(Object *p_parent); + GDVIRTUAL0RC(Dictionary, _get_child_nodes) + 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) + GDVIRTUAL0RC(String, _get_caption) + GDVIRTUAL0RC(bool, _has_filter) + public: virtual void get_parameter_list(List<PropertyInfo> *r_list) const; virtual Variant get_parameter_default_value(const StringName &p_parameter) const; @@ -126,7 +134,7 @@ public: virtual void get_child_nodes(List<ChildNode> *r_child_nodes); - virtual float process(float p_time, bool p_seek); + virtual double process(double p_time, bool p_seek); virtual String get_caption() const; int get_input_count() const; @@ -191,7 +199,7 @@ private: int bone_idx = -1; Vector3 loc; Quaternion rot; - float rot_blend_accum = 0.0; + real_t rot_blend_accum = 0.0; Vector3 scale; TrackCacheTransform() { @@ -210,7 +218,7 @@ private: }; struct TrackCacheBezier : public TrackCache { - float value = 0.0; + real_t value = 0.0; Vector<StringName> subpath; TrackCacheBezier() { type = Animation::TYPE_BEZIER; @@ -219,8 +227,8 @@ private: struct TrackCacheAudio : public TrackCache { bool playing = false; - float start = 0.0; - float len = 0.0; + real_t start = 0.0; + real_t len = 0.0; TrackCacheAudio() { type = Animation::TYPE_AUDIO; @@ -251,7 +259,7 @@ private: void _clear_caches(); bool _update_caches(AnimationPlayer *player); - void _process_graph(float p_delta); + void _process_graph(real_t p_delta); uint64_t setup_pass = 1; uint64_t process_pass = 1; @@ -271,7 +279,7 @@ private: struct Activity { uint64_t last_pass = 0; - float activity = 0.0; + real_t activity = 0.0; }; HashMap<StringName, Vector<Activity>> input_activity_map; @@ -312,8 +320,8 @@ public: Transform3D get_root_motion_transform() const; - float get_connection_activity(const StringName &p_path, int p_connection) const; - void advance(float p_time); + real_t get_connection_activity(const StringName &p_path, int p_connection) const; + void advance(real_t p_time); void rename_parameter(const String &p_base, const String &p_new_base); diff --git a/scene/animation/root_motion_view.h b/scene/animation/root_motion_view.h index 55fd2d2b73..d64c8bc675 100644 --- a/scene/animation/root_motion_view.h +++ b/scene/animation/root_motion_view.h @@ -39,8 +39,8 @@ class RootMotionView : public VisualInstance3D { public: Ref<ImmediateMesh> immediate; NodePath path; - float cell_size = 1.0; - float radius = 10.0; + real_t cell_size = 1.0; + real_t radius = 10.0; bool use_in_game = false; Color color = Color(0.5, 0.5, 1.0); bool first = true; diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index 7bf616e602..3018fd3ae6 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -36,6 +36,10 @@ void Tweener::set_tween(Ref<Tween> p_tween) { tween = p_tween; } +void Tweener::clear_tween() { + tween.unref(); +} + void Tweener::_bind_methods() { ADD_SIGNAL(MethodInfo("finished")); } @@ -46,14 +50,14 @@ void Tween::start_tweeners() { ERR_FAIL_MSG("Tween without commands, aborting."); } - for (List<Ref<Tweener>>::Element *E = tweeners.write[current_step].front(); E; E = E->next()) { - E->get()->start(); + for (Ref<Tweener> &tweener : tweeners.write[current_step]) { + tweener->start(); } } Ref<PropertyTweener> Tween::tween_property(Object *p_target, NodePath p_property, Variant p_to, float p_duration) { ERR_FAIL_NULL_V(p_target, nullptr); - ERR_FAIL_COND_V_MSG(invalid, nullptr, "Tween was created outside the scene tree, can't use Tweeners."); + ERR_FAIL_COND_V_MSG(!valid, nullptr, "Tween invalid. Either finished or created outside scene tree."); ERR_FAIL_COND_V_MSG(started, nullptr, "Can't append to a Tween that has started. Use stop() first."); Ref<PropertyTweener> tweener = memnew(PropertyTweener(p_target, p_property, p_to, p_duration)); @@ -62,7 +66,7 @@ Ref<PropertyTweener> Tween::tween_property(Object *p_target, NodePath p_property } Ref<IntervalTweener> Tween::tween_interval(float p_time) { - ERR_FAIL_COND_V_MSG(invalid, nullptr, "Tween was created outside the scene tree, can't use Tweeners."); + ERR_FAIL_COND_V_MSG(!valid, nullptr, "Tween invalid. Either finished or created outside scene tree."); ERR_FAIL_COND_V_MSG(started, nullptr, "Can't append to a Tween that has started. Use stop() first."); Ref<IntervalTweener> tweener = memnew(IntervalTweener(p_time)); @@ -71,7 +75,7 @@ Ref<IntervalTweener> Tween::tween_interval(float p_time) { } Ref<CallbackTweener> Tween::tween_callback(Callable p_callback) { - ERR_FAIL_COND_V_MSG(invalid, nullptr, "Tween was created outside the scene tree, can't use Tweeners."); + ERR_FAIL_COND_V_MSG(!valid, nullptr, "Tween invalid. Either finished or created outside scene tree."); ERR_FAIL_COND_V_MSG(started, nullptr, "Can't append to a Tween that has started. Use stop() first."); Ref<CallbackTweener> tweener = memnew(CallbackTweener(p_callback)); @@ -80,7 +84,7 @@ Ref<CallbackTweener> Tween::tween_callback(Callable p_callback) { } Ref<MethodTweener> Tween::tween_method(Callable p_callback, float p_from, float p_to, float p_duration) { - ERR_FAIL_COND_V_MSG(invalid, nullptr, "Tween was created outside the scene tree, can't use Tweeners."); + ERR_FAIL_COND_V_MSG(!valid, nullptr, "Tween invalid. Either finished or created outside scene tree."); ERR_FAIL_COND_V_MSG(started, nullptr, "Can't append to a Tween that has started. Use stop() first."); Ref<MethodTweener> tweener = memnew(MethodTweener(p_callback, p_from, p_to, p_duration)); @@ -88,9 +92,7 @@ Ref<MethodTweener> Tween::tween_method(Callable p_callback, float p_from, float return tweener; } -Ref<Tween> Tween::append(Ref<Tweener> p_tweener) { - ERR_FAIL_COND_V_MSG(invalid, nullptr, "Tween was created outside the scene tree, can't use Tweeners."); - ERR_FAIL_COND_V_MSG(started, nullptr, "Can't append to a Tween that has started. Use stop() first."); +void Tween::append(Ref<Tweener> p_tweener) { p_tweener->set_tween(this); if (parallel_enabled) { @@ -102,8 +104,6 @@ Ref<Tween> Tween::append(Ref<Tweener> p_tweener) { tweeners.resize(current_step + 1); tweeners.write[current_step].push_back(p_tweener); - - return this; } void Tween::stop() { @@ -117,7 +117,7 @@ void Tween::pause() { } void Tween::play() { - ERR_FAIL_COND_MSG(invalid, "Tween invalid, can't play."); + ERR_FAIL_COND_MSG(!valid, "Tween invalid. Either finished or created outside scene tree."); ERR_FAIL_COND_MSG(dead, "Can't play finished Tween, use stop() first to reset its state."); running = true; } @@ -132,14 +132,27 @@ bool Tween::is_running() { } void Tween::set_valid(bool p_valid) { - invalid = !p_valid; + valid = p_valid; } bool Tween::is_valid() { - return invalid; + return valid; +} + +void Tween::clear() { + valid = false; + + for (List<Ref<Tweener>> &step : tweeners) { + for (Ref<Tweener> &tweener : step) { + tweener->clear_tween(); + } + } + tweeners.clear(); } Ref<Tween> Tween::bind_node(Node *p_node) { + ERR_FAIL_NULL_V(p_node, this); + bound_node = p_node->get_instance_id(); is_bound = true; return this; @@ -253,18 +266,18 @@ bool Tween::step(float p_delta) { float step_delta = rem_delta; step_active = false; - for (List<Ref<Tweener>>::Element *E = tweeners.write[current_step].front(); E; E = E->next()) { + for (Ref<Tweener> &tweener : tweeners.write[current_step]) { // Modified inside Tweener.step(). float temp_delta = rem_delta; // Turns to true if any Tweener returns true (i.e. is still not finished). - step_active = E->get()->step(temp_delta) || step_active; + step_active = tweener->step(temp_delta) || step_active; step_delta = MIN(temp_delta, rem_delta); } rem_delta = step_delta; if (!step_active) { - emit_signal("step_finished", current_step); + emit_signal(SNAME("step_finished"), current_step); current_step++; if (current_step == tweeners.size()) { @@ -272,9 +285,9 @@ bool Tween::step(float p_delta) { if (loops_done == loops) { running = false; dead = true; - emit_signal("finished"); + emit_signal(SNAME("finished")); } else { - emit_signal("loop_finished", loops_done); + emit_signal(SNAME("loop_finished"), loops_done); current_step = 0; start_tweeners(); } @@ -690,7 +703,7 @@ bool PropertyTweener::step(float &r_delta) { } else { finished = true; r_delta = elapsed_time - delay - duration; - emit_signal("finished"); + emit_signal(SNAME("finished")); return false; } } @@ -745,7 +758,7 @@ bool IntervalTweener::step(float &r_delta) { } else { finished = true; r_delta = elapsed_time - duration; - emit_signal("finished"); + emit_signal(SNAME("finished")); return false; } } @@ -784,7 +797,7 @@ bool CallbackTweener::step(float &r_delta) { finished = true; r_delta = elapsed_time - delay; - emit_signal("finished"); + emit_signal(SNAME("finished")); return false; } @@ -854,7 +867,7 @@ bool MethodTweener::step(float &r_delta) { } else { finished = true; r_delta = elapsed_time - delay - duration; - emit_signal("finished"); + emit_signal(SNAME("finished")); return false; } } diff --git a/scene/animation/tween.h b/scene/animation/tween.h index 947cdb7c2d..953d573539 100644 --- a/scene/animation/tween.h +++ b/scene/animation/tween.h @@ -43,6 +43,7 @@ public: virtual void set_tween(Ref<Tween> p_tween); virtual void start() = 0; virtual bool step(float &r_delta) = 0; + void clear_tween(); protected: static void _bind_methods(); @@ -111,7 +112,7 @@ private: bool started = false; bool running = true; bool dead = false; - bool invalid = true; + bool valid = false; bool default_parallel = false; bool parallel_enabled = false; @@ -128,7 +129,7 @@ public: Ref<IntervalTweener> tween_interval(float p_time); Ref<CallbackTweener> tween_callback(Callable p_callback); Ref<MethodTweener> tween_method(Callable p_callback, float p_from, float p_to, float p_duration); - Ref<Tween> append(Ref<Tweener> p_tweener); + void append(Ref<Tweener> p_tweener); bool custom_step(float p_delta); void stop(); @@ -139,6 +140,7 @@ public: bool is_running(); void set_valid(bool p_valid); bool is_valid(); + void clear(); Ref<Tween> bind_node(Node *p_node); Ref<Tween> set_process_mode(TweenProcessMode p_mode); diff --git a/scene/audio/audio_stream_player.cpp b/scene/audio/audio_stream_player.cpp index 1478cbf69e..0334ba667b 100644 --- a/scene/audio/audio_stream_player.cpp +++ b/scene/audio/audio_stream_player.cpp @@ -31,127 +31,40 @@ #include "audio_stream_player.h" #include "core/config/engine.h" - -void AudioStreamPlayer::_mix_to_bus(const AudioFrame *p_frames, int p_amount) { - int bus_index = AudioServer::get_singleton()->thread_find_bus_index(bus); - - AudioFrame *targets[4] = { nullptr, nullptr, nullptr, nullptr }; - - if (AudioServer::get_singleton()->get_speaker_mode() == AudioServer::SPEAKER_MODE_STEREO) { - targets[0] = AudioServer::get_singleton()->thread_get_channel_mix_buffer(bus_index, 0); - } else { - switch (mix_target) { - case MIX_TARGET_STEREO: { - targets[0] = AudioServer::get_singleton()->thread_get_channel_mix_buffer(bus_index, 0); - } break; - case MIX_TARGET_SURROUND: { - for (int i = 0; i < AudioServer::get_singleton()->get_channel_count(); i++) { - targets[i] = AudioServer::get_singleton()->thread_get_channel_mix_buffer(bus_index, i); - } - } break; - case MIX_TARGET_CENTER: { - targets[0] = AudioServer::get_singleton()->thread_get_channel_mix_buffer(bus_index, 1); - } break; - } - } - - for (int c = 0; c < 4; c++) { - if (!targets[c]) { - break; - } - for (int i = 0; i < p_amount; i++) { - targets[c][i] += p_frames[i]; - } - } -} - -void AudioStreamPlayer::_mix_internal(bool p_fadeout) { - //get data - AudioFrame *buffer = mix_buffer.ptrw(); - int buffer_size = mix_buffer.size(); - - if (p_fadeout) { - // Short fadeout ramp - buffer_size = MIN(buffer_size, 128); - } - - stream_playback->mix(buffer, pitch_scale, buffer_size); - - //multiply volume interpolating to avoid clicks if this changes - float target_volume = p_fadeout ? -80.0 : volume_db; - float vol = Math::db2linear(mix_volume_db); - float vol_inc = (Math::db2linear(target_volume) - vol) / float(buffer_size); - - for (int i = 0; i < buffer_size; i++) { - buffer[i] *= vol; - vol += vol_inc; - } - - //set volume for next mix - mix_volume_db = target_volume; - - _mix_to_bus(buffer, buffer_size); -} - -void AudioStreamPlayer::_mix_audio() { - if (use_fadeout) { - _mix_to_bus(fadeout_buffer.ptr(), fadeout_buffer.size()); - use_fadeout = false; - } - - if (!stream_playback.is_valid() || !active.is_set() || - (stream_paused && !stream_paused_fade)) { - return; - } - - if (stream_paused) { - if (stream_paused_fade && stream_playback->is_playing()) { - _mix_internal(true); - stream_paused_fade = false; - } - return; - } - - if (setstop.is_set()) { - _mix_internal(true); - stream_playback->stop(); - setstop.clear(); - } - - if (setseek.get() >= 0.0 && !stop_has_priority.is_set()) { - if (stream_playback->is_playing()) { - //fade out to avoid pops - _mix_internal(true); - } - - stream_playback->start(setseek.get()); - setseek.set(-1.0); //reset seek - mix_volume_db = volume_db; //reset ramp - } - - stop_has_priority.clear(); - - _mix_internal(false); -} +#include "core/math/audio_frame.h" +#include "servers/audio_server.h" void AudioStreamPlayer::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { - AudioServer::get_singleton()->add_callback(_mix_audios, this); if (autoplay && !Engine::get_singleton()->is_editor_hint()) { play(); } } if (p_what == NOTIFICATION_INTERNAL_PROCESS) { - if (!active.is_set() || (setseek.get() < 0 && !stream_playback->is_playing())) { + Vector<Ref<AudioStreamPlayback>> playbacks_to_remove; + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (playback.is_valid() && !AudioServer::get_singleton()->is_playback_active(playback) && !AudioServer::get_singleton()->is_playback_paused(playback)) { + emit_signal(SNAME("finished")); + playbacks_to_remove.push_back(playback); + } + } + // Now go through and remove playbacks that have finished. Removing elements from a Vector in a range based for is asking for trouble. + for (Ref<AudioStreamPlayback> &playback : playbacks_to_remove) { + stream_playbacks.erase(playback); + } + if (!playbacks_to_remove.is_empty() && stream_playbacks.is_empty()) { + // This node is no longer actively playing audio. active.clear(); set_process_internal(false); - emit_signal("finished"); } } if (p_what == NOTIFICATION_EXIT_TREE) { - AudioServer::get_singleton()->remove_callback(_mix_audios, this); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->stop_playback_stream(playback); + } + stream_playbacks.clear(); } if (p_what == NOTIFICATION_PAUSED) { @@ -167,50 +80,8 @@ void AudioStreamPlayer::_notification(int p_what) { } void AudioStreamPlayer::set_stream(Ref<AudioStream> p_stream) { - AudioServer::get_singleton()->lock(); - - if (active.is_set() && stream_playback.is_valid() && !stream_paused) { - //changing streams out of the blue is not a great idea, but at least - //let's try to somehow avoid a click - - AudioFrame *buffer = fadeout_buffer.ptrw(); - int buffer_size = fadeout_buffer.size(); - - stream_playback->mix(buffer, pitch_scale, buffer_size); - - //multiply volume interpolating to avoid clicks if this changes - float target_volume = -80.0; - float vol = Math::db2linear(mix_volume_db); - float vol_inc = (Math::db2linear(target_volume) - vol) / float(buffer_size); - - for (int i = 0; i < buffer_size; i++) { - buffer[i] *= vol; - vol += vol_inc; - } - - use_fadeout = true; - } - - mix_buffer.resize(AudioServer::get_singleton()->thread_get_mix_buffer_size()); - - if (stream_playback.is_valid()) { - stream_playback.unref(); - stream.unref(); - active.clear(); - setseek.set(-1); - setstop.clear(); - } - - if (p_stream.is_valid()) { - stream = p_stream; - stream_playback = p_stream->instance_playback(); - } - - AudioServer::get_singleton()->unlock(); - - if (p_stream.is_valid() && stream_playback.is_null()) { - stream.unref(); - } + stop(); + stream = p_stream; } Ref<AudioStream> AudioStreamPlayer::get_stream() const { @@ -219,6 +90,11 @@ Ref<AudioStream> AudioStreamPlayer::get_stream() const { void AudioStreamPlayer::set_volume_db(float p_volume) { volume_db = p_volume; + + Vector<AudioFrame> volume_vector = _get_volume_vector(); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_all_bus_volumes_linear(playback, volume_vector); + } } float AudioStreamPlayer::get_volume_db() const { @@ -228,69 +104,94 @@ float AudioStreamPlayer::get_volume_db() const { void AudioStreamPlayer::set_pitch_scale(float p_pitch_scale) { ERR_FAIL_COND(p_pitch_scale <= 0.0); pitch_scale = p_pitch_scale; + + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_pitch_scale(playback, pitch_scale); + } } float AudioStreamPlayer::get_pitch_scale() const { return pitch_scale; } +void AudioStreamPlayer::set_max_polyphony(int p_max_polyphony) { + if (p_max_polyphony > 0) { + max_polyphony = p_max_polyphony; + } +} + +int AudioStreamPlayer::get_max_polyphony() const { + return max_polyphony; +} + void AudioStreamPlayer::play(float p_from_pos) { - if (stream_playback.is_valid()) { - //mix_volume_db = volume_db; do not reset volume ramp here, can cause clicks - setseek.set(p_from_pos); - stop_has_priority.clear(); - active.set(); - set_process_internal(true); + if (stream.is_null()) { + return; + } + ERR_FAIL_COND_MSG(!is_inside_tree(), "Playback can only happen when a node is inside the scene tree"); + if (stream->is_monophonic() && is_playing()) { + stop(); + } + Ref<AudioStreamPlayback> stream_playback = stream->instance_playback(); + ERR_FAIL_COND_MSG(stream_playback.is_null(), "Failed to instantiate playback."); + + AudioServer::get_singleton()->start_playback_stream(stream_playback, bus, _get_volume_vector(), p_from_pos); + stream_playbacks.push_back(stream_playback); + active.set(); + set_process_internal(true); + while (stream_playbacks.size() > max_polyphony) { + AudioServer::get_singleton()->stop_playback_stream(stream_playbacks[0]); + stream_playbacks.remove(0); } } void AudioStreamPlayer::seek(float p_seconds) { - if (stream_playback.is_valid()) { - setseek.set(p_seconds); + if (is_playing()) { + stop(); + play(p_seconds); } } void AudioStreamPlayer::stop() { - if (stream_playback.is_valid() && active.is_set()) { - setstop.set(); - stop_has_priority.set(); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->stop_playback_stream(playback); } + stream_playbacks.clear(); + active.clear(); + set_process_internal(false); } bool AudioStreamPlayer::is_playing() const { - if (stream_playback.is_valid()) { - return active.is_set() && !setstop.is_set(); //&& stream_playback->is_playing(); + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (AudioServer::get_singleton()->is_playback_active(playback)) { + return true; + } } - return false; } float AudioStreamPlayer::get_playback_position() { - if (stream_playback.is_valid()) { - float ss = setseek.get(); - if (ss >= 0.0) { - return ss; - } - return stream_playback->get_playback_position(); + // Return the playback position of the most recently started playback stream. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->get_playback_position(stream_playbacks[stream_playbacks.size() - 1]); } - return 0; } void AudioStreamPlayer::set_bus(const StringName &p_bus) { - //if audio is active, must lock this - AudioServer::get_singleton()->lock(); bus = p_bus; - AudioServer::get_singleton()->unlock(); + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_bus_exclusive(playback, p_bus, _get_volume_vector()); + } } StringName AudioStreamPlayer::get_bus() const { for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) { - if (AudioServer::get_singleton()->get_bus_name(i) == bus) { + if (AudioServer::get_singleton()->get_bus_name(i) == String(bus)) { return bus; } } - return "Master"; + return SNAME("Master"); } void AudioStreamPlayer::set_autoplay(bool p_enable) { @@ -318,18 +219,64 @@ void AudioStreamPlayer::_set_playing(bool p_enable) { } bool AudioStreamPlayer::_is_active() const { - return active.is_set(); + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (AudioServer::get_singleton()->is_playback_active(playback)) { + return true; + } + } + return false; } void AudioStreamPlayer::set_stream_paused(bool p_pause) { - if (p_pause != stream_paused) { - stream_paused = p_pause; - stream_paused_fade = p_pause; + // TODO this does not have perfect recall, fix that maybe? If there are zero playbacks registered with the AudioServer, this bool isn't persisted. + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_paused(playback, p_pause); } } bool AudioStreamPlayer::get_stream_paused() const { - return stream_paused; + // There's currently no way to pause some playback streams but not others. Check the first and don't bother looking at the rest. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->is_playback_paused(stream_playbacks[0]); + } + return false; +} + +Vector<AudioFrame> AudioStreamPlayer::_get_volume_vector() { + Vector<AudioFrame> volume_vector; + // We need at most four stereo pairs (for 7.1 systems). + volume_vector.resize(4); + + // Initialize the volume vector to zero. + for (AudioFrame &channel_volume_db : volume_vector) { + channel_volume_db = AudioFrame(0, 0); + } + + float volume_linear = Math::db2linear(volume_db); + + // Set the volume vector up according to the speaker mode and mix target. + // TODO do we need to scale the volume down when we output to more channels? + if (AudioServer::get_singleton()->get_speaker_mode() == AudioServer::SPEAKER_MODE_STEREO) { + volume_vector.write[0] = AudioFrame(volume_linear, volume_linear); + } else { + switch (mix_target) { + case MIX_TARGET_STEREO: { + volume_vector.write[0] = AudioFrame(volume_linear, volume_linear); + } break; + case MIX_TARGET_SURROUND: { + // TODO Make sure this is right. + volume_vector.write[0] = AudioFrame(volume_linear, volume_linear); + volume_vector.write[1] = AudioFrame(volume_linear, /* LFE= */ 1.0f); + volume_vector.write[2] = AudioFrame(volume_linear, volume_linear); + volume_vector.write[3] = AudioFrame(volume_linear, volume_linear); + } break; + case MIX_TARGET_CENTER: { + // TODO Make sure this is right. + volume_vector.write[1] = AudioFrame(volume_linear, /* LFE= */ 1.0f); + } break; + } + } + return volume_vector; } void AudioStreamPlayer::_validate_property(PropertyInfo &property) const { @@ -352,7 +299,10 @@ void AudioStreamPlayer::_bus_layout_changed() { } Ref<AudioStreamPlayback> AudioStreamPlayer::get_stream_playback() { - return stream_playback; + if (!stream_playbacks.is_empty()) { + return stream_playbacks[stream_playbacks.size() - 1]; + } + return nullptr; } void AudioStreamPlayer::_bind_methods() { @@ -387,6 +337,9 @@ void AudioStreamPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer::set_stream_paused); ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer::get_stream_paused); + ClassDB::bind_method(D_METHOD("set_max_polyphony", "max_polyphony"), &AudioStreamPlayer::set_max_polyphony); + ClassDB::bind_method(D_METHOD("get_max_polyphony"), &AudioStreamPlayer::get_max_polyphony); + 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"); @@ -396,6 +349,7 @@ void AudioStreamPlayer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mix_target", PROPERTY_HINT_ENUM, "Stereo,Surround,Center"), "set_mix_target", "get_mix_target"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_polyphony", PROPERTY_HINT_NONE, ""), "set_max_polyphony", "get_max_polyphony"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus"); ADD_SIGNAL(MethodInfo("finished")); @@ -406,8 +360,6 @@ void AudioStreamPlayer::_bind_methods() { } AudioStreamPlayer::AudioStreamPlayer() { - fadeout_buffer.resize(512); - AudioServer::get_singleton()->connect("bus_layout_changed", callable_mp(this, &AudioStreamPlayer::_bus_layout_changed)); } diff --git a/scene/audio/audio_stream_player.h b/scene/audio/audio_stream_player.h index aa8d088be5..7205fce204 100644 --- a/scene/audio/audio_stream_player.h +++ b/scene/audio/audio_stream_player.h @@ -46,24 +46,16 @@ public: }; private: - Ref<AudioStreamPlayback> stream_playback; + Vector<Ref<AudioStreamPlayback>> stream_playbacks; Ref<AudioStream> stream; - Vector<AudioFrame> mix_buffer; - Vector<AudioFrame> fadeout_buffer; - bool use_fadeout = false; - SafeNumeric<float> setseek{ -1.0 }; SafeFlag active; - SafeFlag setstop; - SafeFlag stop_has_priority; - float mix_volume_db = 0.0; float pitch_scale = 1.0; float volume_db = 0.0; bool autoplay = false; - bool stream_paused = false; - bool stream_paused_fade = false; - StringName bus; + StringName bus = SNAME("Master"); + int max_polyphony = 1; MixTarget mix_target = MIX_TARGET_STEREO; @@ -77,6 +69,8 @@ private: void _bus_layout_changed(); void _mix_to_bus(const AudioFrame *p_frames, int p_amount); + Vector<AudioFrame> _get_volume_vector(); + protected: void _validate_property(PropertyInfo &property) const override; void _notification(int p_what); @@ -92,6 +86,9 @@ public: void set_pitch_scale(float p_pitch_scale); float get_pitch_scale() const; + void set_max_polyphony(int p_max_polyphony); + int get_max_polyphony() const; + void play(float p_from_pos = 0.0); void seek(float p_seconds); void stop(); diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp index 1e121ab6e5..f4e477b613 100644 --- a/scene/debugger/scene_debugger.cpp +++ b/scene/debugger/scene_debugger.cpp @@ -88,13 +88,13 @@ Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Arra } else if (p_msg == "override_camera_2D:transform") { ERR_FAIL_COND_V(p_args.size() < 1, ERR_INVALID_DATA); - Transform2D transform = p_args[1]; + Transform2D transform = p_args[0]; scene_tree->get_root()->set_canvas_transform_override(transform); - +#ifndef _3D_DISABLED } else if (p_msg == "override_camera_3D:set") { ERR_FAIL_COND_V(p_args.size() < 1, ERR_INVALID_DATA); bool enable = p_args[0]; - scene_tree->get_root()->enable_camera_override(enable); + scene_tree->get_root()->enable_camera_3d_override(enable); } else if (p_msg == "override_camera_3D:transform") { ERR_FAIL_COND_V(p_args.size() < 5, ERR_INVALID_DATA); @@ -104,12 +104,12 @@ Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Arra float near = p_args[3]; float far = p_args[4]; if (is_perspective) { - scene_tree->get_root()->set_camera_override_perspective(size_or_fov, near, far); + scene_tree->get_root()->set_camera_3d_override_perspective(size_or_fov, near, far); } else { - scene_tree->get_root()->set_camera_override_orthogonal(size_or_fov, near, far); + scene_tree->get_root()->set_camera_3d_override_orthogonal(size_or_fov, near, far); } - scene_tree->get_root()->set_camera_override_transform(transform); - + scene_tree->get_root()->set_camera_3d_override_transform(transform); +#endif // _3D_DISABLED } else if (p_msg == "set_object_property") { ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA); _set_object_property(p_args[0], p_args[1], p_args[2]); @@ -145,12 +145,12 @@ Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Arra live_editor->_res_set_func(p_args[0], p_args[1], p_args[2]); } else if (p_msg == "live_node_call") { - ERR_FAIL_COND_V(p_args.size() < 7, ERR_INVALID_DATA); - live_editor->_node_call_func(p_args[0], p_args[1], p_args[2], p_args[3], p_args[4], p_args[5], p_args[6]); + ERR_FAIL_COND_V(p_args.size() < 10, ERR_INVALID_DATA); + live_editor->_node_call_func(p_args[0], p_args[1], p_args[2], p_args[3], p_args[4], p_args[5], p_args[6], p_args[7], p_args[8], p_args[9]); } else if (p_msg == "live_res_call") { - ERR_FAIL_COND_V(p_args.size() < 7, ERR_INVALID_DATA); - live_editor->_res_call_func(p_args[0], p_args[1], p_args[2], p_args[3], p_args[4], p_args[5], p_args[6]); + ERR_FAIL_COND_V(p_args.size() < 10, ERR_INVALID_DATA); + live_editor->_res_call_func(p_args[0], p_args[1], p_args[2], p_args[3], p_args[4], p_args[5], p_args[6], p_args[7], p_args[8], p_args[9]); } else if (p_msg == "live_create_node") { ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA); @@ -292,9 +292,9 @@ SceneDebuggerObject::SceneDebuggerObject(ObjectID p_id) { // Add base object properties. List<PropertyInfo> pinfo; obj->get_property_list(&pinfo, true); - for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) { - if (E->get().usage & (PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CATEGORY)) { - properties.push_back(SceneDebuggerProperty(E->get(), obj->get(E->get().name))); + for (const PropertyInfo &E : pinfo) { + if (E.usage & (PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CATEGORY)) { + properties.push_back(SceneDebuggerProperty(E, obj->get(E.name))); } } } @@ -452,8 +452,7 @@ SceneDebuggerTree::SceneDebuggerTree(Node *p_root) { } void SceneDebuggerTree::serialize(Array &p_arr) { - for (List<RemoteNode>::Element *E = nodes.front(); E; E = E->next()) { - RemoteNode &n = E->get(); + for (const RemoteNode &n : nodes) { p_arr.push_back(n.child_count); p_arr.push_back(n.name); p_arr.push_back(n.type_name); diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index c1ae0479f5..bef1011364 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -52,7 +52,7 @@ void BaseButton::_unpress_group() { } } -void BaseButton::_gui_input(Ref<InputEvent> p_event) { +void BaseButton::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (status.disabled) { // no interaction with disabled button @@ -121,31 +121,31 @@ void BaseButton::_notification(int p_what) { } void BaseButton::_pressed() { - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_pressed); - } + GDVIRTUAL_CALL(_pressed); pressed(); - emit_signal("pressed"); + emit_signal(SNAME("pressed")); } void BaseButton::_toggled(bool p_pressed) { - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_toggled, p_pressed); - } + GDVIRTUAL_CALL(_toggled, p_pressed); toggled(p_pressed); - emit_signal("toggled", p_pressed); + emit_signal(SNAME("toggled"), p_pressed); } void BaseButton::on_action_event(Ref<InputEvent> p_event) { if (p_event->is_pressed()) { status.press_attempt = true; status.pressing_inside = true; - emit_signal("button_down"); + emit_signal(SNAME("button_down")); } if (status.press_attempt && status.pressing_inside) { if (toggle_mode) { - if ((p_event->is_pressed() && action_mode == ACTION_MODE_BUTTON_PRESS) || (!p_event->is_pressed() && action_mode == ACTION_MODE_BUTTON_RELEASE)) { + bool is_pressed = p_event->is_pressed(); + if (Object::cast_to<InputEventShortcut>(*p_event)) { + is_pressed = false; + } + if ((is_pressed && action_mode == ACTION_MODE_BUTTON_PRESS) || (!is_pressed && action_mode == ACTION_MODE_BUTTON_RELEASE)) { if (action_mode == ACTION_MODE_BUTTON_PRESS) { status.press_attempt = false; status.pressing_inside = false; @@ -153,7 +153,7 @@ void BaseButton::on_action_event(Ref<InputEvent> p_event) { status.pressed = !status.pressed; _unpress_group(); if (button_group.is_valid()) { - button_group->emit_signal("pressed", this); + button_group->emit_signal(SNAME("pressed"), this); } _toggled(status.pressed); _pressed(); @@ -174,7 +174,7 @@ void BaseButton::on_action_event(Ref<InputEvent> p_event) { } status.press_attempt = false; status.pressing_inside = false; - emit_signal("button_up"); + emit_signal(SNAME("button_up")); } update(); @@ -218,7 +218,7 @@ void BaseButton::set_pressed(bool p_pressed) { if (p_pressed) { _unpress_group(); if (button_group.is_valid()) { - button_group->emit_signal("pressed", this); + button_group->emit_signal(SNAME("pressed"), this); } } _toggled(status.pressed); @@ -226,6 +226,18 @@ void BaseButton::set_pressed(bool p_pressed) { update(); } +void BaseButton::set_pressed_no_signal(bool p_pressed) { + if (!toggle_mode) { + return; + } + if (status.pressed == p_pressed) { + return; + } + status.pressed = p_pressed; + + update(); +} + bool BaseButton::is_pressing() const { return status.press_attempt; } @@ -326,14 +338,14 @@ Ref<Shortcut> BaseButton::get_shortcut() const { return shortcut; } -void BaseButton::_unhandled_key_input(Ref<InputEvent> p_event) { +void BaseButton::unhandled_key_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (!_is_focus_owner_in_shorcut_context()) { return; } - if (!is_disabled() && is_visible_in_tree() && !p_event->is_echo() && shortcut.is_valid() && shortcut->is_shortcut(p_event)) { + if (!is_disabled() && is_visible_in_tree() && !p_event->is_echo() && shortcut.is_valid() && shortcut->matches_event(p_event)) { on_action_event(p_event); accept_event(); } @@ -341,7 +353,7 @@ void BaseButton::_unhandled_key_input(Ref<InputEvent> p_event) { String BaseButton::get_tooltip(const Point2 &p_pos) const { String tooltip = Control::get_tooltip(p_pos); - if (shortcut_in_tooltip && shortcut.is_valid() && shortcut->is_valid()) { + if (shortcut_in_tooltip && shortcut.is_valid() && shortcut->has_valid_event()) { String text = shortcut->get_name() + " (" + shortcut->get_as_text() + ")"; if (tooltip != String() && shortcut->get_name().nocasecmp_to(tooltip) != 0) { text += "\n" + tooltip; @@ -395,10 +407,9 @@ bool BaseButton::_is_focus_owner_in_shorcut_context() const { } void BaseButton::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &BaseButton::_gui_input); - ClassDB::bind_method(D_METHOD("_unhandled_key_input"), &BaseButton::_unhandled_key_input); ClassDB::bind_method(D_METHOD("set_pressed", "pressed"), &BaseButton::set_pressed); ClassDB::bind_method(D_METHOD("is_pressed"), &BaseButton::is_pressed); + ClassDB::bind_method(D_METHOD("set_pressed_no_signal", "pressed"), &BaseButton::set_pressed_no_signal); ClassDB::bind_method(D_METHOD("is_hovered"), &BaseButton::is_hovered); ClassDB::bind_method(D_METHOD("set_toggle_mode", "enabled"), &BaseButton::set_toggle_mode); ClassDB::bind_method(D_METHOD("is_toggle_mode"), &BaseButton::is_toggle_mode); @@ -423,8 +434,8 @@ void BaseButton::_bind_methods() { ClassDB::bind_method(D_METHOD("set_shortcut_context", "node"), &BaseButton::set_shortcut_context); ClassDB::bind_method(D_METHOD("get_shortcut_context"), &BaseButton::get_shortcut_context); - BIND_VMETHOD(MethodInfo("_pressed")); - BIND_VMETHOD(MethodInfo("_toggled", PropertyInfo(Variant::BOOL, "button_pressed"))); + GDVIRTUAL_BIND(_pressed); + GDVIRTUAL_BIND(_toggled, "button_pressed"); ADD_SIGNAL(MethodInfo("pressed")); ADD_SIGNAL(MethodInfo("button_up")); diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h index 6c7a8f3433..cd6e78464f 100644 --- a/scene/gui/base_button.h +++ b/scene/gui/base_button.h @@ -75,12 +75,15 @@ protected: virtual void pressed(); virtual void toggled(bool p_pressed); static void _bind_methods(); - virtual void _gui_input(Ref<InputEvent> p_event); - virtual void _unhandled_key_input(Ref<InputEvent> p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; + virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; void _notification(int p_what); bool _is_focus_owner_in_shorcut_context() const; + GDVIRTUAL0(_pressed) + GDVIRTUAL1(_toggled, bool) + public: enum DrawMode { DRAW_NORMAL, @@ -98,7 +101,8 @@ public: bool is_pressing() const; ///< return whether button is pressed (toggled in) bool is_hovered() const; - void set_pressed(bool p_pressed); ///only works in toggle mode + void set_pressed(bool p_pressed); // Only works in toggle mode. + void set_pressed_no_signal(bool p_pressed); void set_toggle_mode(bool p_on); bool is_toggle_mode() const; diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp index 7407ad5b8f..cf2df4e1a2 100644 --- a/scene/gui/box_container.cpp +++ b/scene/gui/box_container.cpp @@ -43,7 +43,7 @@ void BoxContainer::_resort() { Size2i new_size = get_size(); - int sep = get_theme_constant("separation"); //,vertical?"VBoxContainer":"HBoxContainer"); + int sep = get_theme_constant(SNAME("separation")); //,vertical?"VBoxContainer":"HBoxContainer"); bool rtl = is_layout_rtl(); bool first = true; @@ -247,7 +247,7 @@ Size2 BoxContainer::get_minimum_size() const { /* Calculate MINIMUM SIZE */ Size2i minimum; - int sep = get_theme_constant("separation"); //,vertical?"VBoxContainer":"HBoxContainer"); + int sep = get_theme_constant(SNAME("separation")); //,vertical?"VBoxContainer":"HBoxContainer"); bool first = true; @@ -349,12 +349,13 @@ void BoxContainer::_bind_methods() { MarginContainer *VBoxContainer::add_margin_child(const String &p_label, Control *p_control, bool p_expand) { Label *l = memnew(Label); + l->set_theme_type_variation("HeaderSmall"); l->set_text(p_label); - add_child(l); + add_child(l, false, INTERNAL_MODE_FRONT); MarginContainer *mc = memnew(MarginContainer); mc->add_theme_constant_override("margin_left", 0); mc->add_child(p_control); - add_child(mc); + add_child(mc, false, INTERNAL_MODE_FRONT); if (p_expand) { mc->set_v_size_flags(SIZE_EXPAND_FILL); } diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index bcc273114b..9cdf3bf210 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -41,8 +41,8 @@ Size2 Button::get_minimum_size() const { if (!expand_icon) { Ref<Texture2D> _icon; - if (icon.is_null() && has_theme_icon("icon")) { - _icon = Control::get_theme_icon("icon"); + if (icon.is_null() && has_theme_icon(SNAME("icon"))) { + _icon = Control::get_theme_icon(SNAME("icon")); } else { _icon = icon; } @@ -53,7 +53,7 @@ Size2 Button::get_minimum_size() const { if (icon_align != ALIGN_CENTER) { minsize.width += _icon->get_width(); if (xl_text != "") { - minsize.width += get_theme_constant("hseparation"); + minsize.width += get_theme_constant(SNAME("hseparation")); } } else { minsize.width = MAX(minsize.width, _icon->get_width()); @@ -61,12 +61,12 @@ Size2 Button::get_minimum_size() const { } } - Ref<Font> font = get_theme_font("font"); - float font_height = font->get_height(get_theme_font_size("font_size")); + 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("normal")->get_minimum_size() + minsize; + return get_theme_stylebox(SNAME("normal"))->get_minimum_size() + minsize; } void Button::_set_internal_margin(Side p_side, float p_value) { @@ -79,7 +79,7 @@ void Button::_notification(int p_what) { update(); } break; case NOTIFICATION_TRANSLATION_CHANGED: { - xl_text = tr(text); + xl_text = atr(text); _shape(); minimum_size_changed(); @@ -97,43 +97,43 @@ void Button::_notification(int p_what) { Color color; Color color_icon(1, 1, 1, 1); - Ref<StyleBox> style = get_theme_stylebox("normal"); + Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); bool rtl = is_layout_rtl(); switch (get_draw_mode()) { case DRAW_NORMAL: { - if (rtl && has_theme_stylebox("normal_mirrored")) { - style = get_theme_stylebox("normal_mirrored"); + if (rtl && has_theme_stylebox(SNAME("normal_mirrored"))) { + style = get_theme_stylebox(SNAME("normal_mirrored")); } else { - style = get_theme_stylebox("normal"); + style = get_theme_stylebox(SNAME("normal")); } if (!flat) { style->draw(ci, Rect2(Point2(0, 0), size)); } - color = get_theme_color("font_color"); - if (has_theme_color("icon_normal_color")) { - color_icon = get_theme_color("icon_normal_color"); + color = get_theme_color(SNAME("font_color")); + if (has_theme_color(SNAME("icon_normal_color"))) { + color_icon = get_theme_color(SNAME("icon_normal_color")); } } break; case DRAW_HOVER_PRESSED: { - if (has_theme_stylebox("hover_pressed") && has_theme_stylebox_override("hover_pressed")) { - if (rtl && has_theme_stylebox("hover_pressed_mirrored")) { - style = get_theme_stylebox("hover_pressed_mirrored"); + if (has_theme_stylebox(SNAME("hover_pressed")) && has_theme_stylebox_override("hover_pressed")) { + if (rtl && has_theme_stylebox(SNAME("hover_pressed_mirrored"))) { + style = get_theme_stylebox(SNAME("hover_pressed_mirrored")); } else { - style = get_theme_stylebox("hover_pressed"); + style = get_theme_stylebox(SNAME("hover_pressed")); } if (!flat) { style->draw(ci, Rect2(Point2(0, 0), size)); } - if (has_theme_color("font_hover_pressed_color")) { - color = get_theme_color("font_hover_pressed_color"); + if (has_theme_color(SNAME("font_hover_pressed_color"))) { + color = get_theme_color(SNAME("font_hover_pressed_color")); } else { - color = get_theme_color("font_color"); + color = get_theme_color(SNAME("font_color")); } - if (has_theme_color("icon_hover_pressed_color")) { - color_icon = get_theme_color("icon_hover_pressed_color"); + if (has_theme_color(SNAME("icon_hover_pressed_color"))) { + color_icon = get_theme_color(SNAME("icon_hover_pressed_color")); } break; @@ -141,67 +141,67 @@ void Button::_notification(int p_what) { [[fallthrough]]; } case DRAW_PRESSED: { - if (rtl && has_theme_stylebox("pressed_mirrored")) { - style = get_theme_stylebox("pressed_mirrored"); + if (rtl && has_theme_stylebox(SNAME("pressed_mirrored"))) { + style = get_theme_stylebox(SNAME("pressed_mirrored")); } else { - style = get_theme_stylebox("pressed"); + style = get_theme_stylebox(SNAME("pressed")); } if (!flat) { style->draw(ci, Rect2(Point2(0, 0), size)); } - if (has_theme_color("font_pressed_color")) { - color = get_theme_color("font_pressed_color"); + if (has_theme_color(SNAME("font_pressed_color"))) { + color = get_theme_color(SNAME("font_pressed_color")); } else { - color = get_theme_color("font_color"); + color = get_theme_color(SNAME("font_color")); } - if (has_theme_color("icon_pressed_color")) { - color_icon = get_theme_color("icon_pressed_color"); + if (has_theme_color(SNAME("icon_pressed_color"))) { + color_icon = get_theme_color(SNAME("icon_pressed_color")); } } break; case DRAW_HOVER: { - if (rtl && has_theme_stylebox("hover_mirrored")) { - style = get_theme_stylebox("hover_mirrored"); + if (rtl && has_theme_stylebox(SNAME("hover_mirrored"))) { + style = get_theme_stylebox(SNAME("hover_mirrored")); } else { - style = get_theme_stylebox("hover"); + style = get_theme_stylebox(SNAME("hover")); } if (!flat) { style->draw(ci, Rect2(Point2(0, 0), size)); } - color = get_theme_color("font_hover_color"); - if (has_theme_color("icon_hover_color")) { - color_icon = get_theme_color("icon_hover_color"); + color = get_theme_color(SNAME("font_hover_color")); + if (has_theme_color(SNAME("icon_hover_color"))) { + color_icon = get_theme_color(SNAME("icon_hover_color")); } } break; case DRAW_DISABLED: { - if (rtl && has_theme_stylebox("disabled_mirrored")) { - style = get_theme_stylebox("disabled_mirrored"); + if (rtl && has_theme_stylebox(SNAME("disabled_mirrored"))) { + style = get_theme_stylebox(SNAME("disabled_mirrored")); } else { - style = get_theme_stylebox("disabled"); + style = get_theme_stylebox(SNAME("disabled")); } if (!flat) { style->draw(ci, Rect2(Point2(0, 0), size)); } - color = get_theme_color("font_disabled_color"); - if (has_theme_color("icon_disabled_color")) { - color_icon = get_theme_color("icon_disabled_color"); + color = get_theme_color(SNAME("font_disabled_color")); + if (has_theme_color(SNAME("icon_disabled_color"))) { + color_icon = get_theme_color(SNAME("icon_disabled_color")); } } break; } if (has_focus()) { - Ref<StyleBox> style2 = get_theme_stylebox("focus"); + Ref<StyleBox> style2 = get_theme_stylebox(SNAME("focus")); style2->draw(ci, Rect2(Point2(), size)); } Ref<Texture2D> _icon; - if (icon.is_null() && has_theme_icon("icon")) { - _icon = Control::get_theme_icon("icon"); + if (icon.is_null() && has_theme_icon(SNAME("icon"))) { + _icon = Control::get_theme_icon(SNAME("icon")); } else { _icon = icon; } @@ -234,21 +234,21 @@ void Button::_notification(int p_what) { if (icon_align_rtl_checked == ALIGN_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("hseparation"); + icon_ofs_region = _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("hseparation")); } } else if (icon_align_rtl_checked == ALIGN_CENTER) { style_offset.x = 0.0; } else if (icon_align_rtl_checked == ALIGN_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("hseparation"); + icon_ofs_region = -_internal_margin[SIDE_RIGHT] - get_theme_constant(SNAME("hseparation")); } } style_offset.y = style->get_margin(SIDE_TOP); if (expand_icon) { Size2 _size = get_size() - style->get_offset() * 2; - _size.width -= get_theme_constant("hseparation") + icon_ofs_region; + _size.width -= get_theme_constant(SNAME("hseparation")) + icon_ofs_region; if (!clip_text && icon_align_rtl_checked != ALIGN_CENTER) { _size.width -= text_buf->get_size().width; } @@ -276,7 +276,7 @@ void Button::_notification(int p_what) { } } - Point2 icon_ofs = !_icon.is_null() ? Point2(icon_region.size.width + get_theme_constant("hseparation"), 0) : Point2(); + Point2 icon_ofs = !_icon.is_null() ? Point2(icon_region.size.width + get_theme_constant(SNAME("hseparation")), 0) : Point2(); if (align_rtl_checked == ALIGN_CENTER && icon_align_rtl_checked == ALIGN_CENTER) { icon_ofs.x = 0.0; } @@ -286,10 +286,10 @@ void Button::_notification(int p_what) { int text_width = clip_text ? 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("hseparation"); + text_clip -= _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("hseparation")); } if (_internal_margin[SIDE_RIGHT] > 0) { - text_clip -= _internal_margin[SIDE_RIGHT] + get_theme_constant("hseparation"); + text_clip -= _internal_margin[SIDE_RIGHT] + get_theme_constant(SNAME("hseparation")); } 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; @@ -300,7 +300,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("hseparation"); + text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x + _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("hseparation")); } else { text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x; } @@ -317,7 +317,7 @@ void Button::_notification(int p_what) { } break; case ALIGN_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("hseparation"); + text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width - _internal_margin[SIDE_RIGHT] - get_theme_constant(SNAME("hseparation")); } else { text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width; } @@ -328,8 +328,8 @@ void Button::_notification(int p_what) { } break; } - Color font_outline_color = get_theme_color("font_outline_color"); - int outline_size = get_theme_constant("outline_size"); + Color font_outline_color = get_theme_color(SNAME("font_outline_color")); + int outline_size = get_theme_constant(SNAME("outline_size")); if (outline_size > 0 && font_outline_color.a > 0) { text_buf->draw_outline(ci, text_ofs, outline_size, font_outline_color); } @@ -340,8 +340,8 @@ void Button::_notification(int p_what) { } void Button::_shape() { - Ref<Font> font = get_theme_font("font"); - int font_size = get_theme_font_size("font_size"); + Ref<Font> font = get_theme_font(SNAME("font")); + int font_size = get_theme_font_size(SNAME("font_size")); text_buf->clear(); if (text_direction == Control::TEXT_DIRECTION_INHERITED) { @@ -355,7 +355,7 @@ void Button::_shape() { void Button::set_text(const String &p_text) { if (text != p_text) { text = p_text; - xl_text = tr(text); + xl_text = atr(text); _shape(); update(); diff --git a/scene/gui/check_box.cpp b/scene/gui/check_box.cpp index c0650a8f3f..411fb2e1f0 100644 --- a/scene/gui/check_box.cpp +++ b/scene/gui/check_box.cpp @@ -33,12 +33,14 @@ #include "servers/rendering_server.h" Size2 CheckBox::get_icon_size() const { - Ref<Texture2D> checked = Control::get_theme_icon("checked"); - Ref<Texture2D> checked_disabled = Control::get_theme_icon("checked_disabled"); - Ref<Texture2D> unchecked = Control::get_theme_icon("unchecked"); - Ref<Texture2D> unchecked_disabled = Control::get_theme_icon("unchecked_disabled"); - Ref<Texture2D> radio_checked = Control::get_theme_icon("radio_checked"); - Ref<Texture2D> radio_unchecked = Control::get_theme_icon("radio_unchecked"); + Ref<Texture2D> checked = Control::get_theme_icon(SNAME("checked")); + Ref<Texture2D> unchecked = Control::get_theme_icon(SNAME("unchecked")); + Ref<Texture2D> radio_checked = Control::get_theme_icon(SNAME("radio_checked")); + Ref<Texture2D> radio_unchecked = Control::get_theme_icon(SNAME("radio_unchecked")); + Ref<Texture2D> checked_disabled = Control::get_theme_icon(SNAME("checked_disabled")); + Ref<Texture2D> unchecked_disabled = Control::get_theme_icon(SNAME("unchecked_disabled")); + Ref<Texture2D> radio_checked_disabled = Control::get_theme_icon(SNAME("radio_checked_disabled")); + Ref<Texture2D> radio_unchecked_disabled = Control::get_theme_icon(SNAME("radio_unchecked_disabled")); Size2 tex_size = Size2(0, 0); if (!checked.is_null()) { @@ -53,6 +55,18 @@ Size2 CheckBox::get_icon_size() const { if (!radio_unchecked.is_null()) { tex_size = Size2(MAX(tex_size.width, radio_unchecked->get_width()), MAX(tex_size.height, radio_unchecked->get_height())); } + if (!checked_disabled.is_null()) { + tex_size = Size2(MAX(tex_size.width, checked_disabled->get_width()), MAX(tex_size.height, checked_disabled->get_height())); + } + if (!unchecked_disabled.is_null()) { + tex_size = Size2(MAX(tex_size.width, unchecked_disabled->get_width()), MAX(tex_size.height, unchecked_disabled->get_height())); + } + if (!radio_checked_disabled.is_null()) { + tex_size = Size2(MAX(tex_size.width, radio_checked_disabled->get_width()), MAX(tex_size.height, radio_checked_disabled->get_height())); + } + if (!radio_unchecked_disabled.is_null()) { + tex_size = Size2(MAX(tex_size.width, radio_unchecked_disabled->get_width()), MAX(tex_size.height, radio_unchecked_disabled->get_height())); + } return tex_size; } @@ -61,9 +75,9 @@ 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("hseparation"); + minsize.width += get_theme_constant(SNAME("hseparation")); } - Ref<StyleBox> sb = get_theme_stylebox("normal"); + 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)); return minsize; @@ -83,7 +97,7 @@ void CheckBox::_notification(int p_what) { Ref<Texture2D> on = Control::get_theme_icon(vformat("%s%s", is_radio() ? "radio_checked" : "checked", is_disabled() ? "_disabled" : "")); Ref<Texture2D> off = Control::get_theme_icon(vformat("%s%s", is_radio() ? "radio_unchecked" : "unchecked", is_disabled() ? "_disabled" : "")); - Ref<StyleBox> sb = get_theme_stylebox("normal"); + Ref<StyleBox> sb = get_theme_stylebox(SNAME("normal")); Vector2 ofs; if (is_layout_rtl()) { @@ -91,7 +105,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("check_vadjust"); + ofs.y = int((get_size().height - get_icon_size().height) / 2) + get_theme_constant(SNAME("check_vadjust")); if (is_pressed()) { on->draw(ci, ofs); diff --git a/scene/gui/check_button.cpp b/scene/gui/check_button.cpp index a8bf449355..162a256d23 100644 --- a/scene/gui/check_button.cpp +++ b/scene/gui/check_button.cpp @@ -52,9 +52,9 @@ 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("hseparation"); + minsize.width += get_theme_constant(SNAME("hseparation")); } - Ref<StyleBox> sb = get_theme_stylebox("normal"); + 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)); return minsize; @@ -86,7 +86,7 @@ void CheckButton::_notification(int p_what) { off = Control::get_theme_icon(is_disabled() ? "off_disabled" : "off"); } - Ref<StyleBox> sb = get_theme_stylebox("normal"); + Ref<StyleBox> sb = get_theme_stylebox(SNAME("normal")); Vector2 ofs; Size2 tex_size = get_icon_size(); @@ -95,7 +95,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("check_vadjust"); + ofs.y = (get_size().height - tex_size.height) / 2 + get_theme_constant(SNAME("check_vadjust")); if (is_pressed()) { on->draw(ci, ofs); diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index ba1534ed5c..e7769f9372 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -46,46 +46,74 @@ void CodeEdit::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: case NOTIFICATION_ENTER_TREE: { - set_gutter_width(main_gutter, get_row_height()); - set_gutter_width(line_number_gutter, (line_number_digits + 1) * cache.font->get_char_size('0', 0, cache.font_size).width); - set_gutter_width(fold_gutter, get_row_height() / 1.2); + style_normal = get_theme_stylebox(SNAME("normal")); - breakpoint_color = get_theme_color("breakpoint_color"); - breakpoint_icon = get_theme_icon("breakpoint"); + font = get_theme_font(SNAME("font")); + font_size = get_theme_font_size(SNAME("font_size")); - bookmark_color = get_theme_color("bookmark_color"); - bookmark_icon = get_theme_icon("bookmark"); + line_spacing = get_theme_constant(SNAME("line_spacing")); - executing_line_color = get_theme_color("executing_line_color"); - executing_line_icon = get_theme_icon("executing_line"); + 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(fold_gutter, get_line_height() / 1.2); - line_number_color = get_theme_color("line_number_color"); + breakpoint_color = get_theme_color(SNAME("breakpoint_color")); + breakpoint_icon = get_theme_icon(SNAME("breakpoint")); - folding_color = get_theme_color("code_folding_color"); - can_fold_icon = get_theme_icon("can_fold"); - folded_icon = get_theme_icon("folded"); + bookmark_color = get_theme_color(SNAME("bookmark_color")); + bookmark_icon = get_theme_icon(SNAME("bookmark")); - code_completion_max_width = get_theme_constant("completion_max_width") * cache.font->get_char_size('x').x; - code_completion_max_lines = get_theme_constant("completion_lines"); - code_completion_scroll_width = get_theme_constant("completion_scroll_width"); - code_completion_scroll_color = get_theme_color("completion_scroll_color"); - code_completion_background_color = get_theme_color("completion_background_color"); - code_completion_selected_color = get_theme_color("completion_selected_color"); - code_completion_existing_color = get_theme_color("completion_existing_color"); + executing_line_color = get_theme_color(SNAME("executing_line_color")); + executing_line_icon = get_theme_icon(SNAME("executing_line")); + + line_number_color = get_theme_color(SNAME("line_number_color")); + + folding_color = get_theme_color(SNAME("code_folding_color")); + can_fold_icon = get_theme_icon(SNAME("can_fold")); + folded_icon = get_theme_icon(SNAME("folded")); + + code_completion_max_width = get_theme_constant(SNAME("completion_max_width")); + 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_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")); + + line_length_guideline_color = get_theme_color(SNAME("line_length_guideline_color")); } break; case NOTIFICATION_DRAW: { RID ci = get_canvas_item(); + const Size2 size = get_size(); const bool caret_visible = is_caret_visible(); const bool rtl = is_layout_rtl(); - const int row_height = get_row_height(); + const int row_height = get_line_height(); + + 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 int char_size = (int)font->get_char_size('0', 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(); + if (xoffset > xmargin_beg && xoffset < xmargin_end) { + Color guideline_color = (i == 0) ? line_length_guideline_color : line_length_guideline_color * Color(1, 1, 1, 0.5); + if (rtl) { + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2(size.width - xoffset, 0), Point2(size.width - xoffset, size.height), guideline_color); + continue; + } + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2(xoffset, 0), Point2(xoffset, size.height), guideline_color); + } + } + } bool code_completion_below = false; if (caret_visible && code_completion_active && code_completion_options.size() > 0) { - Ref<StyleBox> csb = get_theme_stylebox("completion"); + Ref<StyleBox> csb = get_theme_stylebox(SNAME("completion")); 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("hseparation", "ItemList"); + const int icon_hsep = get_theme_constant(SNAME("hseparation"), 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; @@ -94,14 +122,14 @@ void CodeEdit::_notification(int p_what) { const Point2 caret_pos = get_caret_draw_pos(); const int total_height = csb->get_minimum_size().y + code_completion_rect.size.height; if (caret_pos.y + row_height + total_height > get_size().height) { - code_completion_rect.position.y = (caret_pos.y - total_height - row_height) + cache.line_spacing; + code_completion_rect.position.y = (caret_pos.y - total_height - row_height) + line_spacing; } else { - code_completion_rect.position.y = caret_pos.y + (cache.line_spacing / 2.0f); + code_completion_rect.position.y = caret_pos.y + (line_spacing / 2.0f); code_completion_below = true; } const int scroll_width = code_completion_options_count > code_completion_max_lines ? code_completion_scroll_width : 0; - const int code_completion_base_width = cache.font->get_string_size(code_completion_base).width; + const int code_completion_base_width = font->get_string_size(code_completion_base).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 { @@ -123,7 +151,7 @@ void CodeEdit::_notification(int p_what) { Ref<TextLine> tl; tl.instantiate(); - tl->add_string(code_completion_options[l].display, cache.font, cache.font_size); + tl->add_string(code_completion_options[l].display, font, font_size); int yofs = (row_height - tl->get_size().y) / 2; Point2 title_pos(code_completion_rect.position.x, code_completion_rect.position.y + i * row_height + yofs); @@ -162,59 +190,65 @@ void CodeEdit::_notification(int p_what) { /* Code hint */ if (caret_visible && code_hint != "" && (!code_completion_active || (code_completion_below != code_hint_draw_below))) { - const Ref<Font> font = cache.font; - const int font_height = font->get_height(cache.font_size); - Ref<StyleBox> sb = get_theme_stylebox("panel", "TooltipPanel"); - Color font_color = get_theme_color("font_color", "TooltipLabel"); + const int font_height = font->get_height(font_size); + Ref<StyleBox> sb = get_theme_stylebox(SNAME("panel"), SNAME("TooltipPanel")); + Color font_color = get_theme_color(SNAME("font_color"), SNAME("TooltipLabel")); Vector<String> code_hint_lines = code_hint.split("\n"); int line_count = code_hint_lines.size(); 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], cache.font_size).x); + max_width = MAX(max_width, font->get_string_size(code_hint_lines[i], font_size).x); } - Size2 minsize = sb->get_minimum_size() + Size2(max_width, line_count * font_height + (cache.line_spacing * line_count - 1)); + 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))), cache.font_size).x; + int offset = font->get_string_size(code_hint_lines[0].substr(0, code_hint_lines[0].find(String::chr(0xFFFF))), font_size).x; if (code_hint_xpos == -0xFFFF) { code_hint_xpos = get_caret_draw_pos().x - offset; } Point2 hint_ofs = Vector2(code_hint_xpos, get_caret_draw_pos().y); if (code_hint_draw_below) { - hint_ofs.y += cache.line_spacing / 2.0f; + hint_ofs.y += line_spacing / 2.0f; } else { - hint_ofs.y -= (minsize.y + row_height) - cache.line_spacing; + hint_ofs.y -= (minsize.y + row_height) - line_spacing; } draw_style_box(sb, Rect2(hint_ofs, minsize)); - int line_spacing = 0; + int yofs = 0; for (int i = 0; i < line_count; i++) { const String &line = code_hint_lines[i]; int begin = 0; int end = 0; if (line.find(String::chr(0xFFFF)) != -1) { - begin = font->get_string_size(line.substr(0, line.find(String::chr(0xFFFF))), cache.font_size).x; - end = font->get_string_size(line.substr(0, line.rfind(String::chr(0xFFFF))), cache.font_size).x; + 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; } - Point2 round_ofs = hint_ofs + sb->get_offset() + Vector2(0, font->get_ascent() + font_height * i + line_spacing); + Point2 round_ofs = hint_ofs + sb->get_offset() + Vector2(0, font->get_ascent() + font_height * i + yofs); round_ofs = round_ofs.round(); - draw_string(font, round_ofs, line.replace(String::chr(0xFFFF), ""), HALIGN_LEFT, -1, cache.font_size, font_color); + draw_string(font, round_ofs, line.replace(String::chr(0xFFFF), ""), HALIGN_LEFT, -1, font_size, font_color); if (end > 0) { - Vector2 b = hint_ofs + sb->get_offset() + Vector2(begin, font_height + font_height * i + line_spacing - 1); - draw_line(b, b + Vector2(end - begin, 0), font_color); + // Draw an underline for the currently edited function parameter. + const Vector2 b = hint_ofs + sb->get_offset() + Vector2(begin, font_height + font_height * i + line_spacing); + draw_line(b, b + Vector2(end - begin, 0), font_color, 2); + + // Draw a translucent text highlight as well. + const Rect2 highlight_rect = Rect2( + hint_ofs + sb->get_offset() + Vector2(begin, 0), + Vector2(end - begin, font_height)); + draw_rect(highlight_rect, font_color * Color(1, 1, 1, 0.2)); } - line_spacing += cache.line_spacing; + yofs += line_spacing; } } } break; } } -void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { +void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { Ref<InputEventMouseButton> mb = p_gui_input; if (mb.is_valid()) { @@ -242,7 +276,7 @@ void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } } break; case MOUSE_BUTTON_LEFT: { - code_completion_current_selected = CLAMP(code_completion_line_ofs + (mb->get_position().y - code_completion_rect.position.y) / get_row_height(), 0, code_completion_options.size() - 1); + code_completion_current_selected = CLAMP(code_completion_line_ofs + (mb->get_position().y - code_completion_rect.position.y) / get_line_height(), 0, code_completion_options.size() - 1); if (mb->is_double_click()) { confirm_code_completion(); } @@ -262,14 +296,15 @@ void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { mpos.x = get_size().x - mpos.x; } - int line, col; - _get_mouse_pos(Point2i(mpos.x, mpos.y), line, col); + Point2i pos = get_line_column_at_pos(Point2i(mpos.x, mpos.y)); + int line = pos.y; + int col = pos.x; if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { if (is_line_folded(line)) { - int wrap_index = get_line_wrap_index_at_col(line, col); - if (wrap_index == times_line_wraps(line)) { - int eol_icon_width = cache.folded_eol_icon->get_width(); + int wrap_index = get_line_wrap_index_at_column(line, col); + if (wrap_index == get_line_wrap_count(line)) { + int eol_icon_width = folded_eol_icon->get_width(); int left_margin = get_total_gutter_width() + eol_icon_width + get_line_width(line, wrap_index) - get_h_scroll(); if (mpos.x > left_margin && mpos.x <= left_margin + eol_icon_width + 3) { unfold_line(line); @@ -278,13 +313,67 @@ void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } } } + } else { + if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->is_command_pressed() && symbol_lookup_word != String()) { + Vector2i mpos = mb->get_position(); + if (is_layout_rtl()) { + mpos.x = get_size().x - mpos.x; + } + + Point2i pos = get_line_column_at_pos(Point2i(mpos.x, mpos.y)); + int line = pos.y; + int col = pos.x; + + emit_signal(SNAME("symbol_lookup"), symbol_lookup_word, line, col); + return; + } + } + } + } + + Ref<InputEventMouseMotion> mm = p_gui_input; + if (mm.is_valid()) { + Vector2i mpos = mm->get_position(); + if (is_layout_rtl()) { + mpos.x = get_size().x - mpos.x; + } + + if (symbol_lookup_on_click_enabled) { + if (mm->is_command_pressed() && mm->get_button_mask() == 0 && !is_dragging_cursor()) { + symbol_lookup_new_word = get_word_at_pos(mpos); + if (symbol_lookup_new_word != symbol_lookup_word) { + emit_signal(SNAME("symbol_validate"), symbol_lookup_new_word); + } + } else { + set_symbol_lookup_word_as_valid(false); + } } } Ref<InputEventKey> k = p_gui_input; bool update_code_completion = false; if (!k.is_valid()) { - TextEdit::_gui_input(p_gui_input); + TextEdit::gui_input(p_gui_input); + return; + } + + /* Ctrl + Hover symbols */ +#ifdef OSX_ENABLED + if (k->get_keycode() == KEY_META) { +#else + if (k->get_keycode() == KEY_CTRL) { +#endif + if (symbol_lookup_on_click_enabled) { + if (k->is_pressed() && !is_dragging_cursor()) { + symbol_lookup_new_word = get_word_at_pos(get_local_mouse_pos()); + if (symbol_lookup_new_word != symbol_lookup_word) { + emit_signal(SNAME("symbol_validate"), symbol_lookup_new_word); + } + } else { + set_symbol_lookup_word_as_valid(false); + } + } return; } @@ -361,7 +450,7 @@ void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } if (k->is_action("ui_text_backspace", true)) { backspace(); - _filter_code_completion_candidates(); + _filter_code_completion_candidates_impl(); accept_event(); return; } @@ -430,25 +519,31 @@ void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { set_code_hint(""); } - TextEdit::_gui_input(p_gui_input); + TextEdit::gui_input(p_gui_input); if (update_code_completion) { - _filter_code_completion_candidates(); + _filter_code_completion_candidates_impl(); } } +/* General overrides */ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const { - if ((code_completion_active && code_completion_rect.has_point(p_pos)) || (is_readonly() && (!is_selecting_enabled() || get_line_count() == 0))) { + if (symbol_lookup_word != String()) { + return CURSOR_POINTING_HAND; + } + + if ((code_completion_active && code_completion_rect.has_point(p_pos)) || (!is_editable() && (!is_selecting_enabled() || get_line_count() == 0))) { return CURSOR_ARROW; } - int line, col; - _get_mouse_pos(p_pos, line, col); + Point2i pos = get_line_column_at_pos(p_pos); + int line = pos.y; + int col = pos.x; if (is_line_folded(line)) { - int wrap_index = get_line_wrap_index_at_col(line, col); - if (wrap_index == times_line_wraps(line)) { - int eol_icon_width = cache.folded_eol_icon->get_width(); + int wrap_index = get_line_wrap_index_at_column(line, col); + if (wrap_index == get_line_wrap_count(line)) { + int eol_icon_width = folded_eol_icon->get_width(); int left_margin = get_total_gutter_width() + eol_icon_width + get_line_width(line, wrap_index) - get_h_scroll(); if (p_pos.x > left_margin && p_pos.x <= left_margin + eol_icon_width + 3) { return CURSOR_POINTING_HAND; @@ -459,6 +554,118 @@ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const { return TextEdit::get_cursor_shape(p_pos); } +/* Text manipulation */ + +// Overridable actions +void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode) { + bool had_selection = has_selection(); + if (had_selection) { + begin_complex_operation(); + delete_selection(); + } + + // Remove the old character if in overtype mode and no selection. + if (is_overtype_mode_enabled() && !had_selection) { + begin_complex_operation(); + + /* Make sure we don't try and remove empty space. */ + if (get_caret_column() < get_line(get_caret_line()).length()) { + remove_text(get_caret_line(), get_caret_column(), get_caret_line(), get_caret_column() + 1); + } + } + + const char32_t chr[2] = { (char32_t)p_unicode, 0 }; + + if (auto_brace_completion_enabled) { + int cl = get_caret_line(); + int cc = get_caret_column(); + int caret_move_offset = 1; + + int post_brace_pair = cc < get_line(cl).length() ? _get_auto_brace_pair_close_at_pos(cl, cc) : -1; + + if (has_string_delimiter(chr) && cc > 0 && _is_char(get_line(cl)[cc - 1]) && post_brace_pair == -1) { + insert_text_at_caret(chr); + } else if (cc < get_line(cl).length() && _is_char(get_line(cl)[cc])) { + insert_text_at_caret(chr); + } else if (post_brace_pair != -1 && auto_brace_completion_pairs[post_brace_pair].close_key[0] == chr[0]) { + caret_move_offset = auto_brace_completion_pairs[post_brace_pair].close_key.length(); + } else if (is_in_comment(cl, cc) != -1 || (is_in_string(cl, cc) != -1 && has_string_delimiter(chr))) { + insert_text_at_caret(chr); + } else { + insert_text_at_caret(chr); + + int pre_brace_pair = _get_auto_brace_pair_open_at_pos(cl, cc + 1); + if (pre_brace_pair != -1) { + insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key); + } + } + set_caret_column(cc + caret_move_offset); + } else { + insert_text_at_caret(chr); + } + + if ((is_overtype_mode_enabled() && !had_selection) || (had_selection)) { + end_complex_operation(); + } +} + +void CodeEdit::_backspace_internal() { + if (!is_editable()) { + return; + } + + int cc = get_caret_column(); + int cl = get_caret_line(); + + if (cc == 0 && cl == 0) { + return; + } + + if (has_selection()) { + delete_selection(); + return; + } + + if (cl > 0 && _is_line_hidden(cl - 1)) { + unfold_line(get_caret_line() - 1); + } + + int prev_line = cc ? cl : cl - 1; + int prev_column = cc ? (cc - 1) : (get_line(cl - 1).length()); + + merge_gutters(prev_line, cl); + + if (auto_brace_completion_enabled && cc > 0) { + int idx = _get_auto_brace_pair_open_at_pos(cl, cc); + if (idx != -1) { + prev_column = cc - auto_brace_completion_pairs[idx].open_key.length(); + + if (_get_auto_brace_pair_close_at_pos(cl, cc) == idx) { + remove_text(prev_line, prev_column, cl, cc + auto_brace_completion_pairs[idx].close_key.length()); + } else { + remove_text(prev_line, prev_column, cl, cc); + } + set_caret_line(prev_line, false, true); + set_caret_column(prev_column); + return; + } + } + + // For space indentation we need to do a simple unindent if there are no chars to the left, acting in the + // same way as tabs. + if (indent_using_spaces && cc != 0) { + if (get_first_non_whitespace_column(cl) >= cc) { + prev_column = cc - _calculate_spaces_till_next_left_indent(cc); + prev_line = cl; + } + } + + remove_text(prev_line, prev_column, cl, cc); + + set_caret_line(prev_line, false, true); + set_caret_column(prev_column); +} + /* Indent management */ void CodeEdit::set_indent_size(const int p_size) { ERR_FAIL_COND_MSG(p_size <= 0, "Indend size must be greater than 0."); @@ -517,28 +724,28 @@ TypedArray<String> CodeEdit::get_auto_indent_prefixes() const { } void CodeEdit::do_indent() { - if (is_readonly()) { + if (!is_editable()) { return; } - if (is_selection_active()) { + if (has_selection()) { indent_lines(); return; } if (!indent_using_spaces) { - _insert_text_at_cursor("\t"); + insert_text_at_caret("\t"); return; } - int spaces_to_add = _calculate_spaces_till_next_right_indent(cursor_get_column()); + int spaces_to_add = _calculate_spaces_till_next_right_indent(get_caret_column()); if (spaces_to_add > 0) { - _insert_text_at_cursor(String(" ").repeat(spaces_to_add)); + insert_text_at_caret(String(" ").repeat(spaces_to_add)); } } void CodeEdit::indent_lines() { - if (is_readonly()) { + if (!is_editable()) { return; } @@ -548,9 +755,9 @@ void CodeEdit::indent_lines() { /* Default is 1 for tab indentation. */ int selection_offset = 1; - int start_line = cursor_get_line(); + int start_line = get_caret_line(); int end_line = start_line; - if (is_selection_active()) { + if (has_selection()) { start_line = get_selection_from_line(); end_line = get_selection_to_line(); @@ -563,7 +770,7 @@ void CodeEdit::indent_lines() { for (int i = start_line; i <= end_line; i++) { const String line_text = get_line(i); - if (line_text.size() == 0 && is_selection_active()) { + if (line_text.size() == 0 && has_selection()) { continue; } @@ -580,32 +787,32 @@ void CodeEdit::indent_lines() { } /* Fix selection and caret being off after shifting selection right.*/ - if (is_selection_active()) { + if (has_selection()) { select(start_line, get_selection_from_column() + selection_offset, get_selection_to_line(), get_selection_to_column() + selection_offset); } - cursor_set_column(cursor_get_column() + selection_offset, false); + set_caret_column(get_caret_column() + selection_offset, false); end_complex_operation(); } void CodeEdit::do_unindent() { - if (is_readonly()) { + if (!is_editable()) { return; } - int cc = cursor_get_column(); + int cc = get_caret_column(); - if (is_selection_active() || cc <= 0) { + if (has_selection() || cc <= 0) { unindent_lines(); return; } - int cl = cursor_get_line(); + int cl = get_caret_line(); const String &line = get_line(cl); if (line[cc - 1] == '\t') { - _remove_text(cl, cc - 1, cl, cc); - cursor_set_column(MAX(0, cc - 1)); + remove_text(cl, cc - 1, cl, cc); + set_caret_column(MAX(0, cc - 1)); return; } @@ -621,13 +828,13 @@ void CodeEdit::do_unindent() { break; } } - _remove_text(cl, cc - spaces_to_remove, cl, cc); - cursor_set_column(MAX(0, cc - spaces_to_remove)); + remove_text(cl, cc - spaces_to_remove, cl, cc); + set_caret_column(MAX(0, cc - spaces_to_remove)); } } void CodeEdit::unindent_lines() { - if (is_readonly()) { + if (!is_editable()) { return; } @@ -638,11 +845,11 @@ void CodeEdit::unindent_lines() { /* therefore we just remember initial values and at the end of the operation offset them by number of removed characters. */ int removed_characters = 0; int initial_selection_end_column = 0; - int initial_cursor_column = cursor_get_column(); + int initial_cursor_column = get_caret_column(); - int start_line = cursor_get_line(); + int start_line = get_caret_line(); int end_line = start_line; - if (is_selection_active()) { + if (has_selection()) { start_line = get_selection_from_line(); end_line = get_selection_to_line(); @@ -685,7 +892,7 @@ void CodeEdit::unindent_lines() { } } - if (is_selection_active()) { + if (has_selection()) { /* Fix selection being off by one on the first line. */ if (first_line_edited) { select(get_selection_from_line(), get_selection_from_column() - removed_characters, get_selection_to_line(), initial_selection_end_column); @@ -696,7 +903,7 @@ void CodeEdit::unindent_lines() { select(get_selection_from_line(), get_selection_from_column(), get_selection_to_line(), initial_selection_end_column - removed_characters); } } - cursor_set_column(initial_cursor_column - removed_characters, false); + set_caret_column(initial_cursor_column - removed_characters, false); end_complex_operation(); } @@ -713,41 +920,13 @@ int CodeEdit::_calculate_spaces_till_next_right_indent(int p_column) const { return indent_size - p_column % indent_size; } -/* TODO: remove once brace completion is refactored. */ -static char32_t _get_right_pair_symbol(char32_t c) { - if (c == '"') { - return '"'; - } - if (c == '\'') { - return '\''; - } - if (c == '(') { - return ')'; - } - if (c == '[') { - return ']'; - } - if (c == '{') { - return '}'; - } - return 0; -} - -static bool _is_pair_left_symbol(char32_t c) { - return c == '"' || - c == '\'' || - c == '(' || - c == '[' || - c == '{'; -} - void CodeEdit::_new_line(bool p_split_current_line, bool p_above) { - if (is_readonly()) { + if (!is_editable()) { return; } - const int cc = cursor_get_column(); - const int cl = cursor_get_line(); + const int cc = get_caret_column(); + const int cl = get_caret_line(); const String line = get_line(cl); String ins = "\n"; @@ -803,16 +982,15 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) { if (should_indent) { ins += indent_text; - /* TODO: Change when brace completion is refactored. */ - char32_t closing_char = _get_right_pair_symbol(indent_char); - if (closing_char != 0 && closing_char == line[cc]) { + String closing_pair = get_auto_brace_completion_close_key(String::chr(indent_char)); + if (!closing_pair.is_empty() && line.find(closing_pair, cc) == cc) { /* No need to move the brace below if we are not taking the text with us. */ if (p_split_current_line) { brace_indent = true; - ins += "\n" + ins.substr(1, ins.length() - 2); + ins += "\n" + ins.substr(indent_text.size(), ins.length() - 2); } else { brace_indent = false; - ins = "\n" + ins.substr(1, ins.length() - 2); + ins = "\n" + ins.substr(indent_text.size(), ins.length() - 2); } } } @@ -824,76 +1002,114 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) { if (!p_split_current_line) { if (p_above) { if (cl > 0) { - cursor_set_line(cl - 1, false); - cursor_set_column(get_line(cursor_get_line()).length()); + set_caret_line(cl - 1, false); + set_caret_column(get_line(get_caret_line()).length()); } else { - cursor_set_column(0); + set_caret_column(0); first_line = true; } } else { - cursor_set_column(line.length()); + set_caret_column(line.length()); } } - insert_text_at_cursor(ins); + insert_text_at_caret(ins); if (first_line) { - cursor_set_line(0); + set_caret_line(0); } else if (brace_indent) { - cursor_set_line(cursor_get_line() - 1, false); - cursor_set_column(get_line(cursor_get_line()).length()); + set_caret_line(get_caret_line() - 1, false); + set_caret_column(get_line(get_caret_line()).length()); } end_complex_operation(); } -void CodeEdit::backspace() { - if (is_readonly()) { - return; - } +/* Auto brace completion */ +void CodeEdit::set_auto_brace_completion_enabled(bool p_enabled) { + auto_brace_completion_enabled = p_enabled; +} - int cc = cursor_get_column(); - int cl = cursor_get_line(); +bool CodeEdit::is_auto_brace_completion_enabled() const { + return auto_brace_completion_enabled; +} - if (cc == 0 && cl == 0) { - return; - } +void CodeEdit::set_highlight_matching_braces_enabled(bool p_enabled) { + highlight_matching_braces_enabled = p_enabled; + update(); +} - if (is_selection_active()) { - delete_selection(); - return; +bool CodeEdit::is_highlight_matching_braces_enabled() const { + return highlight_matching_braces_enabled; +} + +void CodeEdit::add_auto_brace_completion_pair(const String &p_open_key, const String &p_close_key) { + ERR_FAIL_COND_MSG(p_open_key.is_empty(), "auto brace completion open key cannot be empty"); + ERR_FAIL_COND_MSG(p_close_key.is_empty(), "auto brace completion close key cannot be empty"); + + for (int i = 0; i < p_open_key.length(); i++) { + ERR_FAIL_COND_MSG(!is_symbol(p_open_key[i]), "auto brace completion open key must be a symbol"); + } + for (int i = 0; i < p_close_key.length(); i++) { + ERR_FAIL_COND_MSG(!is_symbol(p_close_key[i]), "auto brace completion close key must be a symbol"); } - if (cl > 0 && is_line_hidden(cl - 1)) { - unfold_line(cursor_get_line() - 1); + int at = 0; + for (int i = 0; i < auto_brace_completion_pairs.size(); i++) { + ERR_FAIL_COND_MSG(auto_brace_completion_pairs[i].open_key == p_open_key, "auto brace completion open key '" + p_open_key + "' already exists."); + if (p_open_key.length() < auto_brace_completion_pairs[i].open_key.length()) { + at++; + } } - int prev_line = cc ? cl : cl - 1; - int prev_column = cc ? (cc - 1) : (get_line(cl - 1).length()); + BracePair brace_pair; + brace_pair.open_key = p_open_key; + brace_pair.close_key = p_close_key; + auto_brace_completion_pairs.insert(at, brace_pair); +} - merge_gutters(cl, prev_line); +void CodeEdit::set_auto_brace_completion_pairs(const Dictionary &p_auto_brace_completion_pairs) { + auto_brace_completion_pairs.clear(); - /* TODO: Change when brace completion is refactored. */ - if (auto_brace_completion_enabled && cc > 0 && _is_pair_left_symbol(get_line(cl)[cc - 1])) { - _consume_backspace_for_pair_symbol(prev_line, prev_column); - cursor_set_line(prev_line, false, true); - cursor_set_column(prev_column); - return; + Array keys = p_auto_brace_completion_pairs.keys(); + for (int i = 0; i < keys.size(); i++) { + add_auto_brace_completion_pair(keys[i], p_auto_brace_completion_pairs[keys[i]]); } +} - /* For space indentation we need to do a simple unindent if there are no chars to the left, acting in the */ - /* same way as tabs. */ - if (indent_using_spaces && cc != 0) { - if (get_first_non_whitespace_column(cl) > cc) { - prev_column = cc - _calculate_spaces_till_next_left_indent(cc); - prev_line = cl; +Dictionary CodeEdit::get_auto_brace_completion_pairs() const { + Dictionary brace_pairs; + for (int i = 0; i < auto_brace_completion_pairs.size(); i++) { + brace_pairs[auto_brace_completion_pairs[i].open_key] = auto_brace_completion_pairs[i].close_key; + } + return brace_pairs; +} + +bool CodeEdit::has_auto_brace_completion_open_key(const String &p_open_key) const { + for (int i = 0; i < auto_brace_completion_pairs.size(); i++) { + if (auto_brace_completion_pairs[i].open_key == p_open_key) { + return true; } } + return false; +} - _remove_text(prev_line, prev_column, cl, cc); +bool CodeEdit::has_auto_brace_completion_close_key(const String &p_close_key) const { + for (int i = 0; i < auto_brace_completion_pairs.size(); i++) { + if (auto_brace_completion_pairs[i].close_key == p_close_key) { + return true; + } + } + return false; +} - cursor_set_line(prev_line, false, true); - cursor_set_column(prev_column); +String CodeEdit::get_auto_brace_completion_close_key(const String &p_open_key) const { + for (int i = 0; i < auto_brace_completion_pairs.size(); i++) { + if (auto_brace_completion_pairs[i].open_key == p_open_key) { + return auto_brace_completion_pairs[i].close_key; + } + } + return String(); } /* Main Gutter */ @@ -962,6 +1178,8 @@ void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2 // Breakpoints void CodeEdit::set_line_as_breakpoint(int p_line, bool p_breakpointed) { + ERR_FAIL_INDEX(p_line, get_line_count()); + int mask = get_line_gutter_metadata(p_line, main_gutter); set_line_gutter_metadata(p_line, main_gutter, p_breakpointed ? mask | MAIN_GUTTER_BREAKPOINT : mask & ~MAIN_GUTTER_BREAKPOINT); if (p_breakpointed) { @@ -969,7 +1187,7 @@ void CodeEdit::set_line_as_breakpoint(int p_line, bool p_breakpointed) { } else if (breakpointed_lines.has(p_line)) { breakpointed_lines.erase(p_line); } - emit_signal("breakpoint_toggled", p_line); + emit_signal(SNAME("breakpoint_toggled"), p_line); update(); } @@ -1075,8 +1293,8 @@ void CodeEdit::_line_number_draw_callback(int p_line, int p_gutter, const Rect2 String fc = TS->format_number(String::num(p_line + 1).lpad(line_number_digits, line_number_padding)); Ref<TextLine> tl; tl.instantiate(); - tl->add_string(fc, cache.font, cache.font_size); - int yofs = p_region.position.y + (get_row_height() - tl->get_size().y) / 2; + tl->add_string(fc, font, font_size); + int yofs = p_region.position.y + (get_line_height() - tl->get_size().y) / 2; Color number_color = get_line_gutter_item_color(p_line, line_number_gutter); if (number_color == Color(1, 1, 1)) { number_color = line_number_color; @@ -1116,7 +1334,7 @@ void CodeEdit::_fold_gutter_draw_callback(int p_line, int p_gutter, Rect2 p_regi /* Line Folding */ void CodeEdit::set_line_folding_enabled(bool p_enabled) { line_folding_enabled = p_enabled; - set_hiding_enabled(p_enabled); + _set_hiding_enabled(p_enabled); } bool CodeEdit::is_line_folding_enabled() const { @@ -1133,7 +1351,7 @@ bool CodeEdit::can_fold_line(int p_line) const { return false; } - if (is_line_hidden(p_line) || is_line_folded(p_line)) { + if (_is_line_hidden(p_line) || is_line_folded(p_line)) { return false; } @@ -1182,15 +1400,21 @@ void CodeEdit::fold_line(int p_line) { } /* Find the last line to be hidden. */ - int end_line = get_line_count(); + const int line_count = get_line_count() - 1; + int end_line = line_count; int in_comment = is_in_comment(p_line); int in_string = (in_comment == -1) ? is_in_string(p_line) : -1; if (in_string != -1 || in_comment != -1) { end_line = get_delimiter_end_position(p_line, get_line(p_line).size() - 1).y; - /* End line is the same therefore we have a block. */ + /* End line is the same therefore we have a block of single line delimiters. */ if (end_line == p_line) { - for (int i = p_line + 1; i < get_line_count(); i++) { + for (int i = p_line + 1; i <= line_count; i++) { + if (i == line_count) { + end_line = line_count; + break; + } + if ((in_string != -1 && is_in_string(i) == -1) || (in_comment != -1 && is_in_comment(i) == -1)) { end_line = i - 1; break; @@ -1199,45 +1423,58 @@ void CodeEdit::fold_line(int p_line) { } } else { int start_indent = get_indent_level(p_line); - for (int i = p_line + 1; i < get_line_count(); i++) { + for (int i = p_line + 1; i <= line_count; i++) { if (get_line(p_line).strip_edges().size() == 0 || is_in_string(i) != -1 || is_in_comment(i) != -1) { end_line = i; continue; } - if (get_indent_level(i) <= start_indent && get_line(i).strip_edges().size() != 0) { + if (i == line_count) { + /* Do not fold empty last line of script if any */ + end_line = i; + if (get_line(i).strip_edges().size() == 0) { + end_line--; + } + break; + } + + if ((get_indent_level(i) <= start_indent && get_line(i).strip_edges().size() != 0)) { + /* Keep an empty line unfolded if any */ end_line = i - 1; + if (get_line(i - 1).strip_edges().size() == 0 && i - 2 > p_line) { + end_line = i - 2; + } break; } } } for (int i = p_line + 1; i <= end_line; i++) { - set_line_as_hidden(i, true); + _set_line_as_hidden(i, true); } /* Fix selection. */ - if (is_selection_active()) { - if (is_line_hidden(get_selection_from_line()) && is_line_hidden(get_selection_to_line())) { + if (has_selection()) { + if (_is_line_hidden(get_selection_from_line()) && _is_line_hidden(get_selection_to_line())) { deselect(); - } else if (is_line_hidden(get_selection_from_line())) { + } else if (_is_line_hidden(get_selection_from_line())) { select(p_line, 9999, get_selection_to_line(), get_selection_to_column()); - } else if (is_line_hidden(get_selection_to_line())) { + } else if (_is_line_hidden(get_selection_to_line())) { select(get_selection_from_line(), get_selection_from_column(), p_line, 9999); } } /* Reset caret. */ - if (is_line_hidden(cursor_get_line())) { - cursor_set_line(p_line, false, false); - cursor_set_column(get_line(p_line).length(), false); + if (_is_line_hidden(get_caret_line())) { + set_caret_line(p_line, false, false); + set_caret_column(get_line(p_line).length(), false); } update(); } void CodeEdit::unfold_line(int p_line) { ERR_FAIL_INDEX(p_line, get_line_count()); - if (!is_line_folded(p_line) && !is_line_hidden(p_line)) { + if (!is_line_folded(p_line) && !_is_line_hidden(p_line)) { return; } @@ -1250,10 +1487,10 @@ void CodeEdit::unfold_line(int p_line) { fold_start = is_line_folded(fold_start) ? fold_start : p_line; for (int i = fold_start + 1; i < get_line_count(); i++) { - if (!is_line_hidden(i)) { + if (!_is_line_hidden(i)) { break; } - set_line_as_hidden(i, false); + _set_line_as_hidden(i, false); } update(); } @@ -1266,7 +1503,7 @@ void CodeEdit::fold_all_lines() { } void CodeEdit::unfold_all_lines() { - unhide_all_lines(); + _unhide_all_lines(); } void CodeEdit::toggle_foldable_line(int p_line) { @@ -1280,7 +1517,7 @@ void CodeEdit::toggle_foldable_line(int p_line) { bool CodeEdit::is_line_folded(int p_line) const { ERR_FAIL_INDEX_V(p_line, get_line_count(), false); - return p_line + 1 < get_line_count() && !is_line_hidden(p_line) && is_line_hidden(p_line + 1); + return p_line + 1 < get_line_count() && !_is_line_hidden(p_line) && _is_line_hidden(p_line + 1); } TypedArray<int> CodeEdit::get_folded_lines() const { @@ -1385,7 +1622,8 @@ Point2 CodeEdit::get_delimiter_start_position(int p_line, int p_column) const { } /* Region was found on this line and is not a multiline continuation. */ - if (start_position.x != -1 && start_position.x != get_line(p_line).length() + 1) { + int line_length = get_line(p_line).length(); + if (start_position.x != -1 && line_length > 0 && start_position.x != line_length + 1) { start_position.y = p_line; return start_position; } @@ -1404,7 +1642,8 @@ Point2 CodeEdit::get_delimiter_start_position(int p_line, int p_column) const { start_position.x = delimiter_cache[i].back()->key(); /* Make sure it's not a multiline continuation. */ - if (start_position.x != get_line(i).length() + 1) { + line_length = get_line(i).length(); + if (line_length > 0 && start_position.x != line_length + 1) { break; } } @@ -1504,11 +1743,11 @@ String CodeEdit::get_text_for_code_completion() const { for (int i = 0; i < text_size; i++) { String line = get_line(i); - if (i == cursor_get_line()) { - completion_text += line.substr(0, cursor_get_column()); + if (i == get_caret_line()) { + completion_text += line.substr(0, get_caret_column()); /* Not unicode, represents the caret. */ completion_text += String::chr(0xFFFF); - completion_text += line.substr(cursor_get_column(), line.size()); + completion_text += line.substr(get_caret_column(), line.size()); } else { completion_text += line; } @@ -1521,9 +1760,7 @@ String CodeEdit::get_text_for_code_completion() const { } void CodeEdit::request_code_completion(bool p_force) { - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_request_code_completion")) { - si->call("_request_code_completion", p_force); + if (GDVIRTUAL_CALL(_request_code_completion, p_force)) { return; } @@ -1551,17 +1788,17 @@ void CodeEdit::request_code_completion(bool p_force) { } if (p_force) { - emit_signal("request_code_completion"); + emit_signal(SNAME("request_code_completion")); return; } - String line = get_line(cursor_get_line()); - int ofs = CLAMP(cursor_get_column(), 0, line.length()); + String line = get_line(get_caret_line()); + int ofs = CLAMP(get_caret_column(), 0, line.length()); - if (ofs > 0 && (is_in_string(cursor_get_line(), ofs) != -1 || _is_char(line[ofs - 1]) || code_completion_prefixes.has(String::chr(line[ofs - 1])))) { - emit_signal("request_code_completion"); + if (ofs > 0 && (is_in_string(get_caret_line(), ofs) != -1 || _is_char(line[ofs - 1]) || code_completion_prefixes.has(String::chr(line[ofs - 1])))) { + emit_signal(SNAME("request_code_completion")); } else if (ofs > 1 && line[ofs - 1] == ' ' && code_completion_prefixes.has(String::chr(line[ofs - 2]))) { - emit_signal("request_code_completion"); + emit_signal(SNAME("request_code_completion")); } } @@ -1580,7 +1817,7 @@ void CodeEdit::update_code_completion_options(bool p_forced) { code_completion_forced = p_forced; code_completion_option_sources = code_completion_option_submitted; code_completion_option_submitted.clear(); - _filter_code_completion_candidates(); + _filter_code_completion_candidates_impl(); } TypedArray<Dictionary> CodeEdit::get_code_completion_options() const { @@ -1633,18 +1870,17 @@ void CodeEdit::set_code_completion_selected_index(int p_index) { } void CodeEdit::confirm_code_completion(bool p_replace) { - if (is_readonly() || !code_completion_active) { + if (!is_editable() || !code_completion_active) { return; } - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_confirm_code_completion")) { - si->call("_confirm_code_completion", p_replace); + if (GDVIRTUAL_CALL(_confirm_code_completion, p_replace)) { return; } + begin_complex_operation(); - int caret_line = cursor_get_line(); + int caret_line = get_caret_line(); const String &insert_text = code_completion_options[code_completion_current_selected].insert_text; const String &display_text = code_completion_options[code_completion_current_selected].display; @@ -1652,7 +1888,7 @@ void CodeEdit::confirm_code_completion(bool p_replace) { if (p_replace) { /* Find end of current section */ const String line = get_line(caret_line); - int caret_col = cursor_get_column(); + int caret_col = get_caret_column(); int caret_remove_line = caret_line; bool merge_text = true; @@ -1675,13 +1911,13 @@ void CodeEdit::confirm_code_completion(bool p_replace) { } /* Replace. */ - _remove_text(caret_line, cursor_get_column() - code_completion_base.length(), caret_remove_line, caret_col); - cursor_set_column(cursor_get_column() - code_completion_base.length(), false); - insert_text_at_cursor(insert_text); + remove_text(caret_line, get_caret_column() - code_completion_base.length(), caret_remove_line, caret_col); + set_caret_column(get_caret_column() - code_completion_base.length(), false); + insert_text_at_caret(insert_text); } else { /* Get first non-matching char. */ const String line = get_line(caret_line); - int caret_col = cursor_get_column(); + int caret_col = get_caret_column(); int matching_chars = code_completion_base.length(); for (; matching_chars <= insert_text.length(); matching_chars++) { if (caret_col >= line.length() || line[caret_col] != insert_text[matching_chars]) { @@ -1691,44 +1927,49 @@ void CodeEdit::confirm_code_completion(bool p_replace) { } /* Remove base completion text. */ - _remove_text(caret_line, cursor_get_column() - code_completion_base.length(), caret_line, cursor_get_column()); - cursor_set_column(cursor_get_column() - code_completion_base.length(), false); + remove_text(caret_line, get_caret_column() - code_completion_base.length(), caret_line, get_caret_column()); + set_caret_column(get_caret_column() - code_completion_base.length(), false); /* Merge with text. */ - insert_text_at_cursor(insert_text.substr(0, code_completion_base.length())); - cursor_set_column(caret_col, false); - insert_text_at_cursor(insert_text.substr(matching_chars)); + insert_text_at_caret(insert_text.substr(0, code_completion_base.length())); + set_caret_column(caret_col, false); + insert_text_at_caret(insert_text.substr(matching_chars)); } - /* TODO: merge with autobrace completion, when in CodeEdit. */ /* Handle merging of symbols eg strings, brackets. */ const String line = get_line(caret_line); - char32_t next_char = line[cursor_get_column()]; + char32_t next_char = line[get_caret_column()]; char32_t last_completion_char = insert_text[insert_text.length() - 1]; char32_t last_completion_char_display = display_text[display_text.length() - 1]; - if ((last_completion_char == '"' || last_completion_char == '\'') && (last_completion_char == next_char || last_completion_char_display == next_char)) { - _remove_text(caret_line, cursor_get_column(), caret_line, cursor_get_column() + 1); + int pre_brace_pair = get_caret_column() > 0 ? _get_auto_brace_pair_open_at_pos(caret_line, get_caret_column()) : -1; + int post_brace_pair = get_caret_column() < get_line(caret_line).length() ? _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column()) : -1; + + if (post_brace_pair != -1 && (last_completion_char == next_char || last_completion_char_display == next_char)) { + remove_text(caret_line, get_caret_column(), caret_line, get_caret_column() + 1); } - if (last_completion_char == '(') { - if (next_char == last_completion_char) { - _remove_text(caret_line, cursor_get_column() - 1, caret_line, cursor_get_column()); - } else if (auto_brace_completion_enabled) { - insert_text_at_cursor(")"); - cursor_set_column(cursor_get_column() - 1); - } - } else if (last_completion_char == ')' && next_char == '(') { - _remove_text(caret_line, cursor_get_column() - 2, caret_line, cursor_get_column()); - if (line[cursor_get_column() + 1] != ')') { - cursor_set_column(cursor_get_column() - 1); + if (pre_brace_pair != -1 && pre_brace_pair != post_brace_pair && (last_completion_char == next_char || last_completion_char_display == next_char)) { + remove_text(caret_line, get_caret_column(), caret_line, get_caret_column() + 1); + } else if (auto_brace_completion_enabled && pre_brace_pair != -1 && post_brace_pair == -1) { + insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key); + set_caret_column(get_caret_column() - auto_brace_completion_pairs[pre_brace_pair].close_key.length()); + } + + if (pre_brace_pair == -1 && post_brace_pair == -1 && get_caret_column() > 0 && get_caret_column() < get_line(caret_line).length()) { + pre_brace_pair = _get_auto_brace_pair_open_at_pos(caret_line, get_caret_column() + 1); + if (pre_brace_pair != -1 && pre_brace_pair == _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column() - 1)) { + remove_text(caret_line, get_caret_column() - 2, caret_line, get_caret_column()); + if (_get_auto_brace_pair_close_at_pos(caret_line, get_caret_column() - 1) != pre_brace_pair) { + set_caret_column(get_caret_column() - 1); + } } } end_complex_operation(); cancel_code_completion(); - if (last_completion_char == '(') { + if (code_completion_prefixes.has(String::chr(last_completion_char))) { request_code_completion(); } } @@ -1742,6 +1983,62 @@ void CodeEdit::cancel_code_completion() { update(); } +/* Line length guidelines */ +void CodeEdit::set_line_length_guidelines(TypedArray<int> p_guideline_columns) { + line_length_guideline_columns = p_guideline_columns; + update(); +} + +TypedArray<int> CodeEdit::get_line_length_guidelines() const { + return line_length_guideline_columns; +} + +/* Symbol lookup */ +void CodeEdit::set_symbol_lookup_on_click_enabled(bool p_enabled) { + symbol_lookup_on_click_enabled = p_enabled; + set_symbol_lookup_word_as_valid(false); +} + +bool CodeEdit::is_symbol_lookup_on_click_enabled() const { + return symbol_lookup_on_click_enabled; +} + +String CodeEdit::get_text_for_symbol_lookup() { + Point2i mp = get_local_mouse_pos(); + + Point2i pos = get_line_column_at_pos(mp); + int line = pos.y; + int col = pos.x; + + StringBuilder lookup_text; + const int text_size = get_line_count(); + for (int i = 0; i < text_size; i++) { + String text = get_line(i); + + if (i == line) { + lookup_text += text.substr(0, col); + /* Not unicode, represents the cursor. */ + lookup_text += String::chr(0xFFFF); + lookup_text += text.substr(col, text.size()); + } else { + lookup_text += text; + } + + if (i != text_size - 1) { + lookup_text += "\n"; + } + } + return lookup_text.as_string(); +} + +void CodeEdit::set_symbol_lookup_word_as_valid(bool p_valid) { + symbol_lookup_word = p_valid ? symbol_lookup_new_word : ""; + symbol_lookup_new_word = ""; + if (lookup_symbol_word != symbol_lookup_word) { + _set_symbol_lookup_word(symbol_lookup_word); + } +} + void CodeEdit::_bind_methods() { /* Indent management */ ClassDB::bind_method(D_METHOD("set_indent_size", "size"), &CodeEdit::set_indent_size); @@ -1762,6 +2059,22 @@ void CodeEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("indent_lines"), &CodeEdit::indent_lines); ClassDB::bind_method(D_METHOD("unindent_lines"), &CodeEdit::unindent_lines); + /* Auto brace completion */ + ClassDB::bind_method(D_METHOD("set_auto_brace_completion_enabled", "enable"), &CodeEdit::set_auto_brace_completion_enabled); + ClassDB::bind_method(D_METHOD("is_auto_brace_completion_enabled"), &CodeEdit::is_auto_brace_completion_enabled); + + ClassDB::bind_method(D_METHOD("set_highlight_matching_braces_enabled", "enable"), &CodeEdit::set_highlight_matching_braces_enabled); + ClassDB::bind_method(D_METHOD("is_highlight_matching_braces_enabled"), &CodeEdit::is_highlight_matching_braces_enabled); + + ClassDB::bind_method(D_METHOD("add_auto_brace_completion_pair", "start_key", "end_key"), &CodeEdit::add_auto_brace_completion_pair); + ClassDB::bind_method(D_METHOD("set_auto_brace_completion_pairs", "pairs"), &CodeEdit::set_auto_brace_completion_pairs); + ClassDB::bind_method(D_METHOD("get_auto_brace_completion_pairs"), &CodeEdit::get_auto_brace_completion_pairs); + + ClassDB::bind_method(D_METHOD("has_auto_brace_completion_open_key", "open_key"), &CodeEdit::has_auto_brace_completion_open_key); + ClassDB::bind_method(D_METHOD("has_auto_brace_completion_close_key", "close_key"), &CodeEdit::has_auto_brace_completion_close_key); + + ClassDB::bind_method(D_METHOD("get_auto_brace_completion_close_key", "open_key"), &CodeEdit::get_auto_brace_completion_close_key); + /* Main Gutter */ ClassDB::bind_method(D_METHOD("_main_gutter_draw_callback"), &CodeEdit::_main_gutter_draw_callback); @@ -1886,24 +2199,41 @@ void CodeEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_code_comletion_prefixes"), &CodeEdit::get_code_completion_prefixes); // Overridable - BIND_VMETHOD(MethodInfo("_confirm_code_completion", PropertyInfo(Variant::BOOL, "replace"))); - BIND_VMETHOD(MethodInfo("_request_code_completion", PropertyInfo(Variant::BOOL, "force"))); - BIND_VMETHOD(MethodInfo(Variant::ARRAY, "_filter_code_completion_candidates", PropertyInfo(Variant::ARRAY, "candidates"))); - /* Inspector */ - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_breakpoints_gutter"), "set_draw_breakpoints_gutter", "is_drawing_breakpoints_gutter"); + GDVIRTUAL_BIND(_confirm_code_completion, "replace") + GDVIRTUAL_BIND(_request_code_completion, "force") + GDVIRTUAL_BIND(_filter_code_completion_candidates, "candidates") - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_bookmarks"), "set_draw_bookmarks_gutter", "is_drawing_bookmarks_gutter"); + /* Line length guidelines */ + ClassDB::bind_method(D_METHOD("set_line_length_guidelines", "guideline_columns"), &CodeEdit::set_line_length_guidelines); + ClassDB::bind_method(D_METHOD("get_line_length_guidelines"), &CodeEdit::get_line_length_guidelines); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_executing_lines"), "set_draw_executing_lines_gutter", "is_drawing_executing_lines_gutter"); + /* Symbol lookup */ + ClassDB::bind_method(D_METHOD("set_symbol_lookup_on_click_enabled", "enable"), &CodeEdit::set_symbol_lookup_on_click_enabled); + ClassDB::bind_method(D_METHOD("is_symbol_lookup_on_click_enabled"), &CodeEdit::is_symbol_lookup_on_click_enabled); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_line_numbers"), "set_draw_line_numbers", "is_draw_line_numbers_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "zero_pad_line_numbers"), "set_line_numbers_zero_padded", "is_line_numbers_zero_padded"); + ClassDB::bind_method(D_METHOD("get_text_for_symbol_lookup"), &CodeEdit::get_text_for_symbol_lookup); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_fold_gutter"), "set_draw_fold_gutter", "is_drawing_fold_gutter"); + ClassDB::bind_method(D_METHOD("set_symbol_lookup_word_as_valid", "valid"), &CodeEdit::set_symbol_lookup_word_as_valid); + /* Inspector */ + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "symbol_lookup_on_click"), "set_symbol_lookup_on_click_enabled", "is_symbol_lookup_on_click_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "line_folding"), "set_line_folding_enabled", "is_line_folding_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "line_length_guidelines"), "set_line_length_guidelines", "get_line_length_guidelines"); + + ADD_GROUP("Gutters", "gutters_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gutters_draw_breakpoints_gutter"), "set_draw_breakpoints_gutter", "is_drawing_breakpoints_gutter"); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gutters_draw_bookmarks"), "set_draw_bookmarks_gutter", "is_drawing_bookmarks_gutter"); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gutters_draw_executing_lines"), "set_draw_executing_lines_gutter", "is_drawing_executing_lines_gutter"); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gutters_draw_line_numbers"), "set_draw_line_numbers", "is_draw_line_numbers_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gutters_zero_pad_line_numbers"), "set_line_numbers_zero_padded", "is_line_numbers_zero_padded"); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gutters_draw_fold_gutter"), "set_draw_fold_gutter", "is_drawing_fold_gutter"); + ADD_GROUP("Delimiters", "delimiter_"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "delimiter_strings"), "set_string_delimiters", "get_string_delimiters"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "delimiter_comments"), "set_comment_delimiters", "get_comment_delimiters"); @@ -1918,11 +2248,74 @@ void CodeEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "indent_automatic"), "set_auto_indent_enabled", "is_auto_indent_enabled"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "indent_automatic_prefixes"), "set_auto_indent_prefixes", "get_auto_indent_prefixes"); + ADD_GROUP("Auto brace completion", "auto_brace_completion_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_brace_completion_enabled"), "set_auto_brace_completion_enabled", "is_auto_brace_completion_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_brace_completion_highlight_matching"), "set_highlight_matching_braces_enabled", "is_highlight_matching_braces_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "auto_brace_completion_pairs"), "set_auto_brace_completion_pairs", "get_auto_brace_completion_pairs"); + /* Signals */ + /* Gutters */ ADD_SIGNAL(MethodInfo("breakpoint_toggled", PropertyInfo(Variant::INT, "line"))); + + /* Code Completion */ ADD_SIGNAL(MethodInfo("request_code_completion")); + + /* Symbol lookup */ + ADD_SIGNAL(MethodInfo("symbol_lookup", PropertyInfo(Variant::STRING, "symbol"), PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::INT, "column"))); + ADD_SIGNAL(MethodInfo("symbol_validate", PropertyInfo(Variant::STRING, "symbol"))); } +/* Auto brace completion */ +int CodeEdit::_get_auto_brace_pair_open_at_pos(int p_line, int p_col) { + const String &line = get_line(p_line); + + /* Should be fast enough, expecting low amount of pairs... */ + for (int i = 0; i < auto_brace_completion_pairs.size(); i++) { + const String &open_key = auto_brace_completion_pairs[i].open_key; + if (p_col - open_key.length() < 0) { + continue; + } + + bool is_match = true; + for (int j = 0; j < open_key.length(); j++) { + if (line[(p_col - 1) - j] != open_key[(open_key.length() - 1) - j]) { + is_match = false; + break; + } + } + + if (is_match) { + return i; + } + } + return -1; +} + +int CodeEdit::_get_auto_brace_pair_close_at_pos(int p_line, int p_col) { + const String &line = get_line(p_line); + + /* Should be fast enough, expecting low amount of pairs... */ + for (int i = 0; i < auto_brace_completion_pairs.size(); i++) { + if (p_col + auto_brace_completion_pairs[i].close_key.length() > line.length()) { + continue; + } + + bool is_match = true; + for (int j = 0; j < auto_brace_completion_pairs[i].close_key.length(); j++) { + if (line[p_col + j] != auto_brace_completion_pairs[i].close_key[j]) { + is_match = false; + break; + } + } + + if (is_match) { + return i; + } + } + return -1; +} + +/* Gutters */ void CodeEdit::_gutter_clicked(int p_line, int p_gutter) { if (p_gutter == main_gutter) { if (draw_breakpoints) { @@ -1934,8 +2327,8 @@ void CodeEdit::_gutter_clicked(int p_line, int p_gutter) { if (p_gutter == line_number_gutter) { set_selection_mode(TextEdit::SelectionMode::SELECTION_MODE_LINE, p_line, 0); select(p_line, 0, p_line + 1, 0); - cursor_set_line(p_line + 1); - cursor_set_column(0); + set_caret_line(p_line + 1); + set_caret_column(0); return; } @@ -2128,7 +2521,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()) { - /* If column is specified, loop untill the key is larger then the column. */ + /* If column is specified, loop until the key is larger then the column. */ if (p_column != -1) { if (E->key() > p_column) { break; @@ -2138,7 +2531,7 @@ int CodeEdit::_is_in_delimiter(int p_line, int p_column, DelimiterType p_type) c continue; } - /* If no column, calulate if the entire line is a region */ + /* If no column, calculate if the entire line is a region */ /* excluding whitespace. */ const String line = get_line(p_line); if (!in_region) { @@ -2178,7 +2571,10 @@ int CodeEdit::_is_in_delimiter(int p_line, int p_column, DelimiterType p_type) c } void CodeEdit::_add_delimiter(const String &p_start_key, const String &p_end_key, bool p_line_only, DelimiterType p_type) { - if (p_start_key.length() > 0) { + // If we are the editor allow "null" as a valid start key, otherwise users cannot add delimiters via the inspector. + if (!(Engine::get_singleton()->is_editor_hint() && p_start_key == "null")) { + ERR_FAIL_COND_MSG(p_start_key.is_empty(), "delimiter start key cannot be empty"); + for (int i = 0; i < p_start_key.length(); i++) { ERR_FAIL_COND_MSG(!is_symbol(p_start_key[i]), "delimiter must start with a symbol"); } @@ -2243,7 +2639,11 @@ void CodeEdit::_set_delimiters(const TypedArray<String> &p_delimiters, Delimiter _clear_delimiters(p_type); for (int i = 0; i < p_delimiters.size(); i++) { - String key = p_delimiters[i].is_null() ? "" : p_delimiters[i]; + String key = p_delimiters[i]; + + if (key.is_empty()) { + continue; + } const String start_key = key.get_slice(" ", 0); const String end_key = key.get_slice_count(" ") > 1 ? key.get_slice(" ", 1) : String(); @@ -2278,9 +2678,10 @@ TypedArray<String> CodeEdit::_get_delimiters(DelimiterType p_type) const { } /* Code Completion */ -void CodeEdit::_filter_code_completion_candidates() { - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_filter_code_completion_candidates")) { +void CodeEdit::_filter_code_completion_candidates_impl() { + int line_height = get_line_height(); + + if (GDVIRTUAL_IS_OVERRIDDEN(_filter_code_completion_candidates)) { code_completion_options.clear(); code_completion_base = ""; @@ -2288,19 +2689,21 @@ void CodeEdit::_filter_code_completion_candidates() { TypedArray<Dictionary> completion_options_sources; completion_options_sources.resize(code_completion_option_sources.size()); int i = 0; - for (List<ScriptCodeCompletionOption>::Element *E = code_completion_option_sources.front(); E; E = E->next()) { + for (const ScriptCodeCompletionOption &E : code_completion_option_sources) { Dictionary option; - option["kind"] = E->get().kind; - option["display_text"] = E->get().display; - option["insert_text"] = E->get().insert_text; - option["font_color"] = E->get().font_color; - option["icon"] = E->get().icon; - option["default_value"] = E->get().default_value; + option["kind"] = E.kind; + option["display_text"] = E.display; + option["insert_text"] = E.insert_text; + option["font_color"] = E.font_color; + option["icon"] = E.icon; + option["default_value"] = E.default_value; completion_options_sources[i] = option; i++; } - TypedArray<Dictionary> completion_options = si->call("_filter_code_completion_candidates", completion_options_sources); + Array completion_options; + + GDVIRTUAL_CALL(_filter_code_completion_candidates, completion_options_sources, completion_options); /* No options to complete, cancel. */ if (completion_options.size() == 0) { @@ -2319,19 +2722,24 @@ void CodeEdit::_filter_code_completion_candidates() { option.icon = completion_options[i].get("icon"); option.default_value = completion_options[i].get("default_value"); - max_width = MAX(max_width, cache.font->get_string_size(option.display).width); + int offset = 0; + if (option.default_value.get_type() == Variant::COLOR) { + offset = line_height; + } + + max_width = MAX(max_width, font->get_string_size(option.display, font_size).width + offset); code_completion_options.push_back(option); } - code_completion_longest_line = MIN(max_width, code_completion_max_width); + code_completion_longest_line = MIN(max_width, code_completion_max_width * font_size); code_completion_current_selected = 0; code_completion_active = true; update(); return; } - const int caret_line = cursor_get_line(); - const int caret_column = cursor_get_column(); + const int caret_line = get_caret_line(); + const int caret_column = get_caret_column(); const String line = get_line(caret_line); if (caret_column > 0 && line[caret_column - 1] == '(' && !code_completion_forced) { @@ -2366,7 +2774,7 @@ void CodeEdit::_filter_code_completion_candidates() { /* If we have a space, previous word might be a keyword. eg "func |". */ } else if (cofs > 0 && line[cofs - 1] == ' ') { int ofs = cofs - 1; - while (ofs >= 0 && line[ofs] == ' ') { + while (ofs > 0 && line[ofs] == ' ') { ofs--; } prev_is_word = _is_char(line[ofs]); @@ -2406,13 +2814,16 @@ void CodeEdit::_filter_code_completion_candidates() { int max_width = 0; String string_to_complete_lower = string_to_complete.to_lower(); - for (List<ScriptCodeCompletionOption>::Element *E = code_completion_option_sources.front(); E; E = E->next()) { - ScriptCodeCompletionOption &option = E->get(); - + for (ScriptCodeCompletionOption &option : code_completion_option_sources) { if (single_quote && option.display.is_quoted()) { option.display = option.display.unquote().quote("'"); } + int offset = 0; + if (option.default_value.get_type() == Variant::COLOR) { + offset = line_height; + } + if (in_string != -1) { String quote = single_quote ? "'" : "\""; option.display = option.display.unquote().quote(quote); @@ -2425,7 +2836,7 @@ void CodeEdit::_filter_code_completion_candidates() { if (string_to_complete.length() == 0) { code_completion_options.push_back(option); - max_width = MAX(max_width, cache.font->get_string_size(option.display).width); + max_width = MAX(max_width, font->get_string_size(option.display, font_size).width + offset); continue; } @@ -2474,7 +2885,7 @@ void CodeEdit::_filter_code_completion_candidates() { } else { completion_options_subseq.push_back(option); } - max_width = MAX(max_width, cache.font->get_string_size(option.display).width); + max_width = MAX(max_width, font->get_string_size(option.display, font_size).width + offset); /* Matched the whole subsequence in s_lower. */ } else if (!*ssq_lower) { /* Finished matching in the first s.length() characters. */ @@ -2483,7 +2894,7 @@ void CodeEdit::_filter_code_completion_candidates() { } else { completion_options_subseq_casei.push_back(option); } - max_width = MAX(max_width, cache.font->get_string_size(option.display).width); + max_width = MAX(max_width, font->get_string_size(option.display, font_size).width + offset); } } @@ -2503,7 +2914,7 @@ void CodeEdit::_filter_code_completion_candidates() { return; } - code_completion_longest_line = MIN(max_width, code_completion_max_width); + code_completion_longest_line = MIN(max_width, code_completion_max_width * font_size); code_completion_current_selected = 0; code_completion_active = true; update(); @@ -2516,31 +2927,53 @@ void CodeEdit::_lines_edited_from(int p_from_line, int p_to_line) { return; } + lines_edited_from = (lines_edited_from == -1) ? MIN(p_from_line, p_to_line) : MIN(lines_edited_from, MIN(p_from_line, p_to_line)); + lines_edited_to = (lines_edited_to == -1) ? MAX(p_from_line, p_to_line) : MAX(lines_edited_from, MAX(p_from_line, p_to_line)); +} + +void CodeEdit::_text_set() { + lines_edited_from = 0; + lines_edited_to = 9999; + _text_changed(); +} + +void CodeEdit::_text_changed() { + if (lines_edited_from == -1) { + return; + } + int lc = get_line_count(); line_number_digits = 1; while (lc /= 10) { line_number_digits++; } - set_gutter_width(line_number_gutter, (line_number_digits + 1) * cache.font->get_char_size('0', 0, cache.font_size).width); - int from_line = MIN(p_from_line, p_to_line); - int line_count = (p_to_line - p_from_line); + if (font.is_valid()) { + set_gutter_width(line_number_gutter, (line_number_digits + 1) * font->get_char_size('0', 0, font_size).width); + } + + lc = get_line_count(); + int line_change_size = (lines_edited_to - lines_edited_from); List<int> breakpoints; breakpointed_lines.get_key_list(&breakpoints); - for (const List<int>::Element *E = breakpoints.front(); E; E = E->next()) { - int line = E->get(); - if (line <= from_line) { + for (const int &line : breakpoints) { + if (line < lines_edited_from || (line < lc && is_line_breakpointed(line))) { continue; } + breakpointed_lines.erase(line); + emit_signal(SNAME("breakpoint_toggled"), line); - emit_signal("breakpoint_toggled", line); - if (line_count > 0 || line >= p_from_line) { - emit_signal("breakpoint_toggled", line + line_count); - breakpointed_lines[line + line_count] = true; + int next_line = line + line_change_size; + if (next_line < lc && is_line_breakpointed(next_line)) { + emit_signal(SNAME("breakpoint_toggled"), next_line); + breakpointed_lines[next_line] = true; continue; } } + + lines_edited_from = -1; + lines_edited_to = -1; } CodeEdit::CodeEdit() { @@ -2550,6 +2983,17 @@ CodeEdit::CodeEdit() { auto_indent_prefixes.insert('['); auto_indent_prefixes.insert('('); + /* Auto brace completion */ + add_auto_brace_completion_pair("(", ")"); + add_auto_brace_completion_pair("{", "}"); + add_auto_brace_completion_pair("[", "]"); + add_auto_brace_completion_pair("\"", "\""); + add_auto_brace_completion_pair("\'", "\'"); + + /* Delimiter traking */ + add_string_delimiter("\"", "\"", false); + add_string_delimiter("\'", "\'", false); + /* Text Direction */ set_layout_direction(LAYOUT_DIRECTION_LTR); set_text_direction(TEXT_DIRECTION_LTR); @@ -2562,7 +3006,7 @@ CodeEdit::CodeEdit() { set_gutter_name(gutter_idx, "main_gutter"); set_gutter_draw(gutter_idx, false); set_gutter_overwritable(gutter_idx, true); - set_gutter_type(gutter_idx, GUTTER_TPYE_CUSTOM); + set_gutter_type(gutter_idx, GUTTER_TYPE_CUSTOM); set_gutter_custom_draw(gutter_idx, this, "_main_gutter_draw_callback"); gutter_idx++; @@ -2570,7 +3014,7 @@ CodeEdit::CodeEdit() { add_gutter(); set_gutter_name(gutter_idx, "line_numbers"); set_gutter_draw(gutter_idx, false); - set_gutter_type(gutter_idx, GUTTER_TPYE_CUSTOM); + set_gutter_type(gutter_idx, GUTTER_TYPE_CUSTOM); set_gutter_custom_draw(gutter_idx, this, "_line_number_draw_callback"); gutter_idx++; @@ -2578,13 +3022,15 @@ CodeEdit::CodeEdit() { add_gutter(); set_gutter_name(gutter_idx, "fold_gutter"); set_gutter_draw(gutter_idx, false); - set_gutter_type(gutter_idx, GUTTER_TPYE_CUSTOM); + set_gutter_type(gutter_idx, GUTTER_TYPE_CUSTOM); set_gutter_custom_draw(gutter_idx, this, "_fold_gutter_draw_callback"); gutter_idx++; connect("lines_edited_from", callable_mp(this, &CodeEdit::_lines_edited_from)); - connect("gutter_clicked", callable_mp(this, &CodeEdit::_gutter_clicked)); + connect("text_set", callable_mp(this, &CodeEdit::_text_set)); + connect("text_changed", callable_mp(this, &CodeEdit::_text_changed)); + connect("gutter_clicked", callable_mp(this, &CodeEdit::_gutter_clicked)); connect("gutter_added", callable_mp(this, &CodeEdit::_update_gutter_indexes)); connect("gutter_removed", callable_mp(this, &CodeEdit::_update_gutter_indexes)); _update_gutter_indexes(); diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 25b518402b..740548d559 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -66,6 +66,19 @@ private: void _new_line(bool p_split_current_line = true, bool p_above = false); + /* Auto brace completion */ + bool auto_brace_completion_enabled = false; + + /* BracePair open_key must be uniquie and ordered by length. */ + struct BracePair { + String open_key = ""; + String close_key = ""; + }; + Vector<BracePair> auto_brace_completion_pairs; + + int _get_auto_brace_pair_open_at_pos(int p_line, int p_col); + int _get_auto_brace_pair_close_at_pos(int p_line, int p_col); + /* Main Gutter */ enum MainGutterType { MAIN_GUTTER_BREAKPOINT = 0x01, @@ -112,7 +125,7 @@ private: void _update_gutter_indexes(); /* Line Folding */ - bool line_folding_enabled = true; + bool line_folding_enabled = false; /* Delimiters */ enum DelimiterType { @@ -206,17 +219,52 @@ private: List<ScriptCodeCompletionOption> code_completion_option_sources; String code_completion_base; - void _filter_code_completion_candidates(); + void _filter_code_completion_candidates_impl(); + + /* Line length guidelines */ + TypedArray<int> line_length_guideline_columns; + Color line_length_guideline_color; + + /* Symbol lookup */ + bool symbol_lookup_on_click_enabled = false; + + String symbol_lookup_new_word = ""; + String symbol_lookup_word = ""; + + /* Visual */ + Ref<StyleBox> style_normal; + + Ref<Font> font; + int font_size = 16; + + int line_spacing = 1; + + /* Callbacks */ + int lines_edited_from = -1; + int lines_edited_to = -1; void _lines_edited_from(int p_from_line, int p_to_line); + void _text_set(); + void _text_changed(); protected: - void _gui_input(const Ref<InputEvent> &p_gui_input) override; void _notification(int p_what); static void _bind_methods(); + /* Text manipulation */ + + // Overridable actions + virtual void _handle_unicode_input_internal(const uint32_t p_unicode) override; + virtual void _backspace_internal() override; + + GDVIRTUAL1(_confirm_code_completion, bool) + GDVIRTUAL1(_request_code_completion, bool) + GDVIRTUAL1RC(Array, _filter_code_completion_candidates, TypedArray<Dictionary>) + public: + /* General overrides */ + virtual void gui_input(const Ref<InputEvent> &p_gui_input) override; virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override; /* Indent management */ @@ -238,7 +286,21 @@ public: void indent_lines(); void unindent_lines(); - virtual void backspace() override; + /* Auto brace completion */ + void set_auto_brace_completion_enabled(bool p_enabled); + bool is_auto_brace_completion_enabled() const; + + void set_highlight_matching_braces_enabled(bool p_enabled); + bool is_highlight_matching_braces_enabled() const; + + void add_auto_brace_completion_pair(const String &p_open_key, const String &p_close_key); + void set_auto_brace_completion_pairs(const Dictionary &p_auto_brace_completion_pairs); + Dictionary get_auto_brace_completion_pairs() const; + + bool has_auto_brace_completion_open_key(const String &p_open_key) const; + bool has_auto_brace_completion_close_key(const String &p_close_key) const; + + String get_auto_brace_completion_close_key(const String &p_open_key) const; /* Main Gutter */ void set_draw_breakpoints_gutter(bool p_draw); @@ -347,6 +409,18 @@ public: void confirm_code_completion(bool p_replace = false); void cancel_code_completion(); + /* Line length guidelines */ + void set_line_length_guidelines(TypedArray<int> p_guideline_columns); + TypedArray<int> get_line_length_guidelines() const; + + /* Symbol lookup */ + void set_symbol_lookup_on_click_enabled(bool p_enabled); + bool is_symbol_lookup_on_click_enabled() const; + + String get_text_for_symbol_lookup(); + + void set_symbol_lookup_word_as_valid(bool p_valid); + CodeEdit(); ~CodeEdit(); }; diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 049de4c8c5..1afb0b8e9d 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -40,40 +40,47 @@ #endif #include "scene/main/window.h" +List<Color> ColorPicker::preset_cache; + void ColorPicker::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: { - btn_pick->set_icon(get_theme_icon("screen_picker", "ColorPicker")); - bt_add_preset->set_icon(get_theme_icon("add_preset")); - + btn_pick->set_icon(get_theme_icon(SNAME("screen_picker"), SNAME("ColorPicker"))); + btn_add_preset->set_icon(get_theme_icon(SNAME("add_preset"))); + _update_presets(); _update_controls(); } break; case NOTIFICATION_ENTER_TREE: { - btn_pick->set_icon(get_theme_icon("screen_picker", "ColorPicker")); - bt_add_preset->set_icon(get_theme_icon("add_preset")); + btn_pick->set_icon(get_theme_icon(SNAME("screen_picker"), SNAME("ColorPicker"))); + btn_add_preset->set_icon(get_theme_icon(SNAME("add_preset"))); _update_controls(); _update_color(); #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { - PackedColorArray saved_presets = EditorSettings::get_singleton()->get_project_metadata("color_picker", "presets", PackedColorArray()); + if (preset_cache.is_empty()) { + PackedColorArray saved_presets = EditorSettings::get_singleton()->get_project_metadata("color_picker", "presets", PackedColorArray()); + for (int i = 0; i < saved_presets.size(); i++) { + preset_cache.push_back(saved_presets[i]); + } + } - for (int i = 0; i < saved_presets.size(); i++) { - add_preset(saved_presets[i]); + for (int i = 0; i < preset_cache.size(); i++) { + presets.push_back(preset_cache[i]); } } #endif } break; case NOTIFICATION_PARENTED: { for (int i = 0; i < 4; i++) { - set_offset((Side)i, get_offset((Side)i) + get_theme_constant("margin")); + set_offset((Side)i, get_offset((Side)i) + get_theme_constant(SNAME("margin"))); } } break; case NOTIFICATION_VISIBILITY_CHANGED: { Popup *p = Object::cast_to<Popup>(get_parent()); if (p) { - p->set_size(Size2(get_combined_minimum_size().width + get_theme_constant("margin") * 2, get_combined_minimum_size().height + get_theme_constant("margin") * 2)); + p->set_size(Size2(get_combined_minimum_size().width + get_theme_constant(SNAME("margin")) * 2, get_combined_minimum_size().height + get_theme_constant(SNAME("margin")) * 2)); } } break; case NOTIFICATION_WM_CLOSE_REQUEST: { @@ -89,43 +96,53 @@ Ref<Shader> ColorPicker::circle_shader; void ColorPicker::init_shaders() { wheel_shader.instantiate(); - wheel_shader->set_code( - "shader_type canvas_item;" - "void fragment() {" - " float x = UV.x - 0.5;" - " float y = UV.y - 0.5;" - " float a = atan(y, x);" - " x += 0.001;" - " y += 0.001;" - " float b = float(sqrt(x * x + y * y) < 0.5) * float(sqrt(x * x + y * y) > 0.42);" - " x -= 0.002;" - " float b2 = float(sqrt(x * x + y * y) < 0.5) * float(sqrt(x * x + y * y) > 0.42);" - " y -= 0.002;" - " float b3 = float(sqrt(x * x + y * y) < 0.5) * float(sqrt(x * x + y * y) > 0.42);" - " x += 0.002;" - " float b4 = float(sqrt(x * x + y * y) < 0.5) * float(sqrt(x * x + y * y) > 0.42);" - " COLOR = vec4(clamp((abs(fract(((a - TAU) / TAU) + vec3(3.0, 2.0, 1.0) / 3.0) * 6.0 - 3.0) - 1.0), 0.0, 1.0), (b + b2 + b3 + b4) / 4.00);" - "}"); + wheel_shader->set_code(R"( +// ColorPicker wheel shader. + +shader_type canvas_item; + +void fragment() { + float x = UV.x - 0.5; + float y = UV.y - 0.5; + float a = atan(y, x); + x += 0.001; + y += 0.001; + float b = float(sqrt(x * x + y * y) < 0.5) * float(sqrt(x * x + y * y) > 0.42); + x -= 0.002; + float b2 = float(sqrt(x * x + y * y) < 0.5) * float(sqrt(x * x + y * y) > 0.42); + y -= 0.002; + float b3 = float(sqrt(x * x + y * y) < 0.5) * float(sqrt(x * x + y * y) > 0.42); + x += 0.002; + float b4 = float(sqrt(x * x + y * y) < 0.5) * float(sqrt(x * x + y * y) > 0.42); + + COLOR = vec4(clamp((abs(fract(((a - TAU) / TAU) + vec3(3.0, 2.0, 1.0) / 3.0) * 6.0 - 3.0) - 1.0), 0.0, 1.0), (b + b2 + b3 + b4) / 4.00); +} +)"); circle_shader.instantiate(); - circle_shader->set_code( - "shader_type canvas_item;" - "uniform float v = 1.0;" - "void fragment() {" - " float x = UV.x - 0.5;" - " float y = UV.y - 0.5;" - " float a = atan(y, x);" - " 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);" - " 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_shader->set_code(R"( +// ColorPicker circle shader. + +shader_type canvas_item; + +uniform float v = 1.0; + +void fragment() { + float x = UV.x - 0.5; + float y = UV.y - 0.5; + float a = atan(y, x); + 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); + + 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); +})"); } void ColorPicker::finish_shaders() { @@ -134,7 +151,7 @@ void ColorPicker::finish_shaders() { } void ColorPicker::set_focus_on_line_edit() { - c_text->call_deferred("grab_focus"); + c_text->call_deferred(SNAME("grab_focus")); } void ColorPicker::_update_controls() { @@ -172,7 +189,7 @@ void ColorPicker::_update_controls() { } } else { Ref<StyleBoxEmpty> style_box_empty(memnew(StyleBoxEmpty)); - Ref<Texture2D> bar_arrow = get_theme_icon("bar_arrow"); + 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); @@ -286,7 +303,7 @@ void ColorPicker::_value_changed(double) { } _set_pick_color(color, false); - emit_signal("color_changed", color); + emit_signal(SNAME("color_changed"), color); } void ColorPicker::_html_submitted(const String &p_html) { @@ -305,7 +322,7 @@ void ColorPicker::_html_submitted(const String &p_html) { } set_pick_color(color); - emit_signal("color_changed", color); + emit_signal(SNAME("color_changed"), color); } void ColorPicker::_update_color(bool p_update_sliders) { @@ -358,29 +375,30 @@ void ColorPicker::_update_color(bool p_update_sliders) { } void ColorPicker::_update_presets() { - return; - //presets should be shown using buttons or something else, this method is not a good idea - - presets_per_row = 10; - Size2 size = bt_add_preset->get_size(); - Size2 preset_size = Size2(MIN(size.width * presets.size(), presets_per_row * size.width), size.height * (Math::ceil((float)presets.size() / presets_per_row))); - preset->set_custom_minimum_size(preset_size); - preset_container->set_custom_minimum_size(preset_size); - preset->draw_rect(Rect2(Point2(), preset_size), Color(1, 1, 1, 0)); - - for (int i = 0; i < presets.size(); i++) { - int x = (i % presets_per_row) * size.width; - int y = (Math::floor((float)i / presets_per_row)) * size.height; - preset->draw_rect(Rect2(Point2(x, y), size), presets[i]); + int preset_size = _get_preset_size(); + // Only update the preset button size if it has changed. + if (preset_size != prev_preset_size) { + prev_preset_size = preset_size; + btn_add_preset->set_custom_minimum_size(Size2(preset_size, preset_size)); + for (int i = 1; i < preset_container->get_child_count(); i++) { + ColorPresetButton *cpb = Object::cast_to<ColorPresetButton>(preset_container->get_child(i)); + cpb->set_custom_minimum_size(Size2(preset_size, preset_size)); + } + } + // Only load preset buttons when the only child is the add-preset button. + if (preset_container->get_child_count() == 1) { + for (int i = 0; i < preset_cache.size(); i++) { + _add_preset_button(preset_size, preset_cache[i]); + } + _notification(NOTIFICATION_VISIBILITY_CHANGED); } - _notification(NOTIFICATION_VISIBILITY_CHANGED); } void ColorPicker::_text_type_toggled() { text_is_constructor = !text_is_constructor; if (text_is_constructor) { text_type->set_text(""); - text_type->set_icon(get_theme_icon("Script", "EditorIcons")); + text_type->set_icon(get_theme_icon(SNAME("Script"), SNAME("EditorIcons"))); c_text->set_editable(false); } else { @@ -408,13 +426,37 @@ ColorPicker::PickerShapeType ColorPicker::get_picker_shape() const { return picker_type; } +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; +} + +void ColorPicker::_add_preset_button(int p_size, const Color &p_color) { + ColorPresetButton *btn_preset = memnew(ColorPresetButton(p_color)); + btn_preset->set_preset_color(p_color); + btn_preset->set_custom_minimum_size(Size2(p_size, p_size)); + btn_preset->connect("gui_input", callable_mp(this, &ColorPicker::_preset_input), varray(p_color)); + btn_preset->set_tooltip(vformat(RTR("Color: #%s\nLMB: Apply color\nRMB: Remove preset"), p_color.to_html(p_color.a < 1))); + preset_container->add_child(btn_preset); +} + void ColorPicker::add_preset(const Color &p_color) { if (presets.find(p_color)) { presets.move_to_back(presets.find(p_color)); + + // Find button to move to the end. + for (int i = 1; i < preset_container->get_child_count(); i++) { + ColorPresetButton *current_btn = Object::cast_to<ColorPresetButton>(preset_container->get_child(i)); + if (current_btn && p_color == current_btn->get_preset_color()) { + preset_container->move_child(current_btn, preset_container->get_child_count() - 1); + break; + } + } } else { presets.push_back(p_color); + preset_cache.push_back(p_color); + + _add_preset_button(_get_preset_size(), p_color); } - preset->update(); #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { @@ -427,7 +469,16 @@ void ColorPicker::add_preset(const Color &p_color) { void ColorPicker::erase_preset(const Color &p_color) { if (presets.find(p_color)) { presets.erase(presets.find(p_color)); - preset->update(); + preset_cache.erase(preset_cache.find(p_color)); + + // Find preset button to remove. + for (int i = 1; i < preset_container->get_child_count(); i++) { + ColorPresetButton *current_btn = Object::cast_to<ColorPresetButton>(preset_container->get_child(i)); + if (current_btn && p_color == current_btn->get_preset_color()) { + current_btn->queue_delete(); + break; + } + } #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { @@ -527,7 +578,7 @@ void ColorPicker::_sample_input(const Ref<InputEvent> &p_event) { // Revert to the old color when left-clicking the old color sample. color = old_color; _update_color(); - emit_signal("color_changed", color); + emit_signal(SNAME("color_changed"), color); } } } @@ -544,28 +595,28 @@ void ColorPicker::_sample_draw() { const Rect2 rect_old = Rect2(Point2(), Size2(sample->get_size().width * 0.5, sample->get_size().height * 0.95)); if (display_old_color && old_color.a < 1.0) { - sample->draw_texture_rect(get_theme_icon("preset_bg", "ColorPicker"), rect_old, true); + sample->draw_texture_rect(get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), rect_old, true); } sample->draw_rect(rect_old, old_color); if (old_color.r > 1 || old_color.g > 1 || old_color.b > 1) { // Draw an indicator to denote that the old color is "overbright" and can't be displayed accurately in the preview. - sample->draw_texture(get_theme_icon("overbright_indicator", "ColorPicker"), Point2()); + sample->draw_texture(get_theme_icon(SNAME("overbright_indicator"), SNAME("ColorPicker")), Point2()); } } else { rect_new = Rect2(Point2(), Size2(sample->get_size().width, sample->get_size().height * 0.95)); } if (color.a < 1.0) { - sample->draw_texture_rect(get_theme_icon("preset_bg", "ColorPicker"), rect_new, true); + sample->draw_texture_rect(get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), rect_new, true); } sample->draw_rect(rect_new, color); if (color.r > 1 || color.g > 1 || color.b > 1) { // Draw an indicator to denote that the new color is "overbright" and can't be displayed accurately in the preview. - sample->draw_texture(get_theme_icon("overbright_indicator", "ColorPicker"), Point2(uv_edit->get_size().width * 0.5, 0)); + sample->draw_texture(get_theme_icon(SNAME("overbright_indicator"), SNAME("ColorPicker")), Point2(uv_edit->get_size().width * 0.5, 0)); } } @@ -639,7 +690,7 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) { default: { } } - Ref<Texture2D> cursor = get_theme_icon("picker_cursor", "ColorPicker"); + Ref<Texture2D> cursor = get_theme_icon(SNAME("picker_cursor"), SNAME("ColorPicker")); int x; int y; if (picker_type == SHAPE_VHS_CIRCLE) { @@ -669,7 +720,7 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) { } else if (p_which == 1) { if (picker_type == SHAPE_HSV_RECTANGLE) { - Ref<Texture2D> hue = get_theme_icon("color_hue", "ColorPicker"); + 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; @@ -718,7 +769,7 @@ void ColorPicker::_slider_draw(int p_which) { #endif if (p_which == 3) { - scroll[p_which]->draw_texture_rect(get_theme_icon("preset_bg", "ColorPicker"), Rect2(Point2(0, margin), Size2(size.x, margin)), true); + 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; @@ -730,7 +781,7 @@ void ColorPicker::_slider_draw(int p_which) { } if (hsv_mode_enabled) { if (p_which == 0) { - Ref<Texture2D> hue = get_theme_icon("color_hue", "ColorPicker"); + 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; @@ -816,10 +867,10 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { set_pick_color(color); _update_color(); if (!deferred_mode_enabled) { - emit_signal("color_changed", color); + emit_signal(SNAME("color_changed"), color); } } else if (deferred_mode_enabled && !bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) { - emit_signal("color_changed", color); + emit_signal(SNAME("color_changed"), color); changing_color = false; spinning = false; } else { @@ -863,7 +914,7 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { set_pick_color(color); _update_color(); if (!deferred_mode_enabled) { - emit_signal("color_changed", color); + emit_signal(SNAME("color_changed"), color); } } } @@ -888,9 +939,9 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { set_pick_color(color); _update_color(); if (!deferred_mode_enabled) { - emit_signal("color_changed", color); + emit_signal(SNAME("color_changed"), color); } else if (!bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) { - emit_signal("color_changed", color); + emit_signal(SNAME("color_changed"), color); } } @@ -911,47 +962,23 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { set_pick_color(color); _update_color(); if (!deferred_mode_enabled) { - emit_signal("color_changed", color); + emit_signal(SNAME("color_changed"), color); } } } -void ColorPicker::_preset_input(const Ref<InputEvent> &p_event) { +void ColorPicker::_preset_input(const Ref<InputEvent> &p_event, const Color &p_color) { Ref<InputEventMouseButton> bev = p_event; if (bev.is_valid()) { - int index = 0; if (bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) { - for (int i = 0; i < presets.size(); i++) { - int x = (i % presets_per_row) * bt_add_preset->get_size().x; - int y = (Math::floor((float)i / presets_per_row)) * bt_add_preset->get_size().y; - if (bev->get_position().x > x && bev->get_position().x < x + preset->get_size().x && bev->get_position().y > y && bev->get_position().y < y + preset->get_size().y) { - index = i; - } - } - set_pick_color(presets[index]); + set_pick_color(p_color); _update_color(); - emit_signal("color_changed", color); + emit_signal(SNAME("color_changed"), p_color); } else if (bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_RIGHT && presets_enabled) { - index = bev->get_position().x / (preset->get_size().x / presets.size()); - Color clicked_preset = presets[index]; - erase_preset(clicked_preset); - emit_signal("preset_removed", clicked_preset); - bt_add_preset->show(); - } - } - - Ref<InputEventMouseMotion> mev = p_event; - - if (mev.is_valid()) { - int index = mev->get_position().x * presets.size(); - if (preset->get_size().x != 0) { - index /= preset->get_size().x; - } - if (index < 0 || index >= presets.size()) { - return; + erase_preset(p_color); + emit_signal(SNAME("preset_removed"), p_color); } - preset->set_tooltip(vformat(RTR("Color: #%s\nLMB: Set color\nRMB: Remove preset"), presets[index].to_html(presets[index].a < 1))); } } @@ -962,7 +989,7 @@ void ColorPicker::_screen_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> bev = p_event; if (bev.is_valid() && bev->get_button_index() == MOUSE_BUTTON_LEFT && !bev->is_pressed()) { - emit_signal("color_changed", color); + emit_signal(SNAME("color_changed"), color); screen->hide(); } @@ -985,7 +1012,7 @@ void ColorPicker::_screen_input(const Ref<InputEvent> &p_event) { void ColorPicker::_add_preset_pressed() { add_preset(color); - emit_signal("preset_added", color); + emit_signal(SNAME("preset_added"), color); } void ColorPicker::_screen_pick_pressed() { @@ -1002,7 +1029,7 @@ void ColorPicker::_screen_pick_pressed() { 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("connect", "hidden", Callable(btn_pick, "set_pressed"), varray(false)); + screen->call_deferred(SNAME("connect"), "hidden", Callable(btn_pick, "set_pressed"), varray(false)); } screen->raise(); #ifndef _MSC_VER @@ -1038,7 +1065,7 @@ void ColorPicker::_focus_exit() { } void ColorPicker::_html_focus_exit() { - if (c_text->get_menu()->is_visible()) { + if (c_text->is_menu_visible()) { return; } _html_submitted(c_text->get_text()); @@ -1048,11 +1075,11 @@ void ColorPicker::_html_focus_exit() { void ColorPicker::set_presets_enabled(bool p_enabled) { presets_enabled = p_enabled; if (!p_enabled) { - bt_add_preset->set_disabled(true); - bt_add_preset->set_focus_mode(FOCUS_NONE); + btn_add_preset->set_disabled(true); + btn_add_preset->set_focus_mode(FOCUS_NONE); } else { - bt_add_preset->set_disabled(false); - bt_add_preset->set_focus_mode(FOCUS_ALL); + btn_add_preset->set_disabled(false); + btn_add_preset->set_focus_mode(FOCUS_ALL); } } @@ -1064,7 +1091,6 @@ void ColorPicker::set_presets_visible(bool p_visible) { presets_visible = p_visible; preset_separator->set_visible(p_visible); preset_container->set_visible(p_visible); - preset_container2->set_visible(p_visible); } bool ColorPicker::are_presets_visible() const { @@ -1113,7 +1139,7 @@ void ColorPicker::_bind_methods() { ColorPicker::ColorPicker() : BoxContainer(true) { HBoxContainer *hb_edit = memnew(HBoxContainer); - add_child(hb_edit); + add_child(hb_edit, false, INTERNAL_MODE_FRONT); hb_edit->set_v_size_flags(SIZE_EXPAND_FILL); hb_edit->add_child(uv_edit); @@ -1121,11 +1147,11 @@ ColorPicker::ColorPicker() : uv_edit->set_mouse_filter(MOUSE_FILTER_PASS); uv_edit->set_h_size_flags(SIZE_EXPAND_FILL); uv_edit->set_v_size_flags(SIZE_EXPAND_FILL); - uv_edit->set_custom_minimum_size(Size2(get_theme_constant("sv_width"), get_theme_constant("sv_height"))); + uv_edit->set_custom_minimum_size(Size2(get_theme_constant(SNAME("sv_width")), get_theme_constant(SNAME("sv_height")))); uv_edit->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw), make_binds(0, uv_edit)); HBoxContainer *hb_smpl = memnew(HBoxContainer); - add_child(hb_smpl); + add_child(hb_smpl, false, INTERNAL_MODE_FRONT); hb_smpl->add_child(sample); sample->set_h_size_flags(SIZE_EXPAND_FILL); @@ -1139,19 +1165,19 @@ ColorPicker::ColorPicker() : btn_pick->connect("pressed", callable_mp(this, &ColorPicker::_screen_pick_pressed)); VBoxContainer *vbl = memnew(VBoxContainer); - add_child(vbl); + add_child(vbl, false, INTERNAL_MODE_FRONT); - add_child(memnew(HSeparator)); + add_child(memnew(HSeparator), false, INTERNAL_MODE_FRONT); VBoxContainer *vbr = memnew(VBoxContainer); - add_child(vbr); + 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("label_width"), 0)); + 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]); @@ -1210,7 +1236,7 @@ ColorPicker::ColorPicker() : wheel_edit->set_h_size_flags(SIZE_EXPAND_FILL); wheel_edit->set_v_size_flags(SIZE_EXPAND_FILL); - wheel_edit->set_custom_minimum_size(Size2(get_theme_constant("sv_width"), get_theme_constant("sv_height"))); + wheel_edit->set_custom_minimum_size(Size2(get_theme_constant(SNAME("sv_width")), get_theme_constant(SNAME("sv_height")))); hb_edit->add_child(wheel_edit); wheel_mat.instantiate(); @@ -1235,7 +1261,7 @@ ColorPicker::ColorPicker() : wheel_uv->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw), make_binds(0, wheel_uv)); hb_edit->add_child(w_edit); - w_edit->set_custom_minimum_size(Size2(get_theme_constant("h_width"), 0)); + w_edit->set_custom_minimum_size(Size2(get_theme_constant(SNAME("h_width")), 0)); 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)); @@ -1247,20 +1273,16 @@ ColorPicker::ColorPicker() : set_pick_color(Color(1, 1, 1)); - add_child(preset_separator); + add_child(preset_separator, false, INTERNAL_MODE_FRONT); preset_container->set_h_size_flags(SIZE_EXPAND_FILL); - add_child(preset_container); - - preset_container->add_child(preset); - preset->connect("gui_input", callable_mp(this, &ColorPicker::_preset_input)); - preset->connect("draw", callable_mp(this, &ColorPicker::_update_presets)); + preset_container->set_columns(preset_column_count); + add_child(preset_container, false, INTERNAL_MODE_FRONT); - preset_container2->set_h_size_flags(SIZE_EXPAND_FILL); - add_child(preset_container2); - preset_container2->add_child(bt_add_preset); - bt_add_preset->set_tooltip(RTR("Add current color as a preset.")); - bt_add_preset->connect("pressed", callable_mp(this, &ColorPicker::_add_preset_pressed)); + btn_add_preset->set_icon_align(Button::ALIGN_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); } ///////////////// @@ -1275,11 +1297,11 @@ void ColorPickerButton::_about_to_popup() { void ColorPickerButton::_color_changed(const Color &p_color) { color = p_color; update(); - emit_signal("color_changed", color); + emit_signal(SNAME("color_changed"), color); } void ColorPickerButton::_modal_closed() { - emit_signal("popup_closed"); + emit_signal(SNAME("popup_closed")); set_pressed(false); } @@ -1287,6 +1309,7 @@ void ColorPickerButton::pressed() { _update_picker(); popup->set_as_minsize(); + picker->_update_presets(); Rect2i usable_rect = popup->get_usable_parent_rect(); //let's try different positions to see which one we can use @@ -1317,14 +1340,14 @@ void ColorPickerButton::pressed() { void ColorPickerButton::_notification(int p_what) { switch (p_what) { case NOTIFICATION_DRAW: { - const Ref<StyleBox> normal = get_theme_stylebox("normal"); + const Ref<StyleBox> normal = get_theme_stylebox(SNAME("normal")); const Rect2 r = Rect2(normal->get_offset(), get_size() - normal->get_minimum_size()); - draw_texture_rect(Control::get_theme_icon("bg", "ColorPickerButton"), r, true); + draw_texture_rect(Control::get_theme_icon(SNAME("bg"), SNAME("ColorPickerButton")), r, true); draw_rect(r, color); if (color.r > 1 || color.g > 1 || color.b > 1) { // Draw an indicator to denote that the color is "overbright" and can't be displayed accurately in the preview - draw_texture(Control::get_theme_icon("overbright_indicator", "ColorPicker"), normal->get_offset()); + draw_texture(Control::get_theme_icon(SNAME("overbright_indicator"), SNAME("ColorPicker")), normal->get_offset()); } } break; case NOTIFICATION_WM_CLOSE_REQUEST: { @@ -1382,14 +1405,14 @@ void ColorPickerButton::_update_picker() { picker = memnew(ColorPicker); picker->set_anchors_and_offsets_preset(PRESET_WIDE); popup->add_child(picker); - add_child(popup); + add_child(popup, false, INTERNAL_MODE_FRONT); picker->connect("color_changed", callable_mp(this, &ColorPickerButton::_color_changed)); popup->connect("about_to_popup", callable_mp(this, &ColorPickerButton::_about_to_popup)); popup->connect("popup_hide", callable_mp(this, &ColorPickerButton::_modal_closed)); picker->set_pick_color(color); picker->set_edit_alpha(edit_alpha); picker->set_display_old_color(true); - emit_signal("picker_created"); + emit_signal(SNAME("picker_created")); } } @@ -1412,3 +1435,64 @@ void ColorPickerButton::_bind_methods() { ColorPickerButton::ColorPickerButton() { set_toggle_mode(true); } + +///////////////// + +void ColorPresetButton::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_DRAW: { + const Rect2 r = Rect2(Point2(0, 0), get_size()); + Ref<StyleBox> sb_raw = get_theme_stylebox(SNAME("preset_fg"), SNAME("ColorPresetButton"))->duplicate(); + Ref<StyleBoxFlat> sb_flat = sb_raw; + Ref<StyleBoxTexture> sb_texture = sb_raw; + + if (sb_flat.is_valid()) { + if (preset_color.a < 1) { + // Draw a background pattern when the color is transparent. + sb_flat->set_bg_color(Color(1, 1, 1)); + sb_flat->draw(get_canvas_item(), r); + + Rect2 bg_texture_rect = r.grow_side(SIDE_LEFT, -sb_flat->get_margin(SIDE_LEFT)); + bg_texture_rect = bg_texture_rect.grow_side(SIDE_RIGHT, -sb_flat->get_margin(SIDE_RIGHT)); + bg_texture_rect = bg_texture_rect.grow_side(SIDE_TOP, -sb_flat->get_margin(SIDE_TOP)); + bg_texture_rect = bg_texture_rect.grow_side(SIDE_BOTTOM, -sb_flat->get_margin(SIDE_BOTTOM)); + + draw_texture_rect(get_theme_icon(SNAME("preset_bg"), SNAME("ColorPresetButton")), bg_texture_rect, true); + sb_flat->set_bg_color(preset_color); + } + sb_flat->set_bg_color(preset_color); + sb_flat->draw(get_canvas_item(), r); + } else if (sb_texture.is_valid()) { + if (preset_color.a < 1) { + // Draw a background pattern when the color is transparent. + bool use_tile_texture = (sb_texture->get_h_axis_stretch_mode() == StyleBoxTexture::AxisStretchMode::AXIS_STRETCH_MODE_TILE) || (sb_texture->get_h_axis_stretch_mode() == StyleBoxTexture::AxisStretchMode::AXIS_STRETCH_MODE_TILE_FIT); + draw_texture_rect(get_theme_icon(SNAME("preset_bg"), SNAME("ColorPresetButton")), r, use_tile_texture); + } + sb_texture->set_modulate(preset_color); + sb_texture->draw(get_canvas_item(), r); + } else { + WARN_PRINT("Unsupported StyleBox used for ColorPresetButton. Use StyleBoxFlat or StyleBoxTexture instead."); + } + if (preset_color.r > 1 || preset_color.g > 1 || preset_color.b > 1) { + // Draw an indicator to denote that the color is "overbright" and can't be displayed accurately in the preview + draw_texture(Control::get_theme_icon(SNAME("overbright_indicator"), SNAME("ColorPresetButton")), Vector2(0, 0)); + } + + } break; + } +} + +void ColorPresetButton::set_preset_color(const Color &p_color) { + preset_color = p_color; +} + +Color ColorPresetButton::get_preset_color() const { + return preset_color; +} + +ColorPresetButton::ColorPresetButton(Color p_color) { + preset_color = p_color; +} + +ColorPresetButton::~ColorPresetButton() { +} diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h index 3bd2ff9375..67ca007eb5 100644 --- a/scene/gui/color_picker.h +++ b/scene/gui/color_picker.h @@ -35,6 +35,7 @@ #include "scene/gui/box_container.h" #include "scene/gui/button.h" #include "scene/gui/check_button.h" +#include "scene/gui/grid_container.h" #include "scene/gui/label.h" #include "scene/gui/line_edit.h" #include "scene/gui/popup.h" @@ -43,6 +44,22 @@ #include "scene/gui/spin_box.h" #include "scene/gui/texture_rect.h" +class ColorPresetButton : public BaseButton { + GDCLASS(ColorPresetButton, BaseButton); + + Color preset_color; + +protected: + void _notification(int); + +public: + void set_preset_color(const Color &p_color); + Color get_preset_color() const; + + ColorPresetButton(Color p_color); + ~ColorPresetButton(); +}; + class ColorPicker : public BoxContainer { GDCLASS(ColorPicker, BoxContainer); @@ -58,6 +75,7 @@ public: private: static Ref<Shader> wheel_shader; static Ref<Shader> circle_shader; + static List<Color> preset_cache; Control *screen = nullptr; Control *uv_edit = memnew(Control); @@ -68,12 +86,9 @@ private: Control *wheel = memnew(Control); Control *wheel_uv = memnew(Control); TextureRect *sample = memnew(TextureRect); - TextureRect *preset = memnew(TextureRect); - HBoxContainer *preset_container = memnew(HBoxContainer); - HBoxContainer *preset_container2 = memnew(HBoxContainer); + GridContainer *preset_container = memnew(GridContainer); HSeparator *preset_separator = memnew(HSeparator); - Button *bt_add_preset = memnew(Button); - List<Color> presets; + Button *btn_add_preset = memnew(Button); Button *btn_pick = memnew(Button); CheckButton *btn_hsv = memnew(CheckButton); CheckButton *btn_raw = memnew(CheckButton); @@ -82,14 +97,19 @@ private: Label *labels[4]; Button *text_type = memnew(Button); LineEdit *c_text = memnew(LineEdit); + bool edit_alpha = true; Size2i ms; bool text_is_constructor = false; - int presets_per_row = 0; PickerShapeType picker_type = SHAPE_HSV_WHEEL; + const int preset_column_count = 9; + int prev_preset_size = 0; + List<Color> presets; + Color color; Color old_color; + bool display_old_color = false; bool raw_mode_enabled = false; bool hsv_mode_enabled = false; @@ -99,6 +119,7 @@ private: bool spinning = false; bool presets_enabled = true; bool presets_visible = true; + float h = 0.0; float s = 0.0; float v = 0.0; @@ -108,7 +129,6 @@ private: void _value_changed(double); void _update_controls(); void _update_color(bool p_update_sliders = true); - void _update_presets(); void _update_text_value(); void _text_type_toggled(); void _sample_input(const Ref<InputEvent> &p_event); @@ -118,7 +138,7 @@ private: void _uv_input(const Ref<InputEvent> &p_event, Control *c); void _w_input(const Ref<InputEvent> &p_event); - void _preset_input(const Ref<InputEvent> &p_event); + void _preset_input(const Ref<InputEvent> &p_event, const Color &p_color); void _screen_input(const Ref<InputEvent> &p_event); void _add_preset_pressed(); void _screen_pick_pressed(); @@ -126,6 +146,9 @@ private: void _focus_exit(); void _html_focus_exit(); + inline int _get_preset_size(); + void _add_preset_button(int p_size, const Color &p_color); + protected: void _notification(int); static void _bind_methods(); @@ -151,6 +174,7 @@ public: void add_preset(const Color &p_color); void erase_preset(const Color &p_color); PackedColorArray get_presets() const; + void _update_presets(); void set_hsv_mode(bool p_enabled); bool is_hsv_mode() const; diff --git a/scene/gui/container.cpp b/scene/gui/container.cpp index dea69aae6b..c97434f69b 100644 --- a/scene/gui/container.cpp +++ b/scene/gui/container.cpp @@ -47,9 +47,9 @@ void Container::add_child_notify(Node *p_child) { return; } - control->connect("size_flags_changed", callable_mp(this, &Container::queue_sort)); - control->connect("minimum_size_changed", callable_mp(this, &Container::_child_minsize_changed)); - control->connect("visibility_changed", callable_mp(this, &Container::_child_minsize_changed)); + control->connect(SNAME("size_flags_changed"), callable_mp(this, &Container::queue_sort)); + control->connect(SNAME("minimum_size_changed"), callable_mp(this, &Container::_child_minsize_changed)); + control->connect(SNAME("visibility_changed"), callable_mp(this, &Container::_child_minsize_changed)); minimum_size_changed(); queue_sort(); diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index d42b505f7b..81411d5844 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -30,6 +30,7 @@ #include "control.h" +#include "container.h" #include "core/config/project_settings.h" #include "core/math/geometry_2d.h" #include "core/object/message_queue.h" @@ -168,6 +169,20 @@ Size2 Control::_edit_get_minimum_size() const { } #endif +String Control::properties_managed_by_container[] = { + "offset_left", + "offset_top", + "offset_right", + "offset_bottom", + "anchor_left", + "anchor_top", + "anchor_right", + "anchor_bottom", + "rect_position", + "rect_scale", + "rect_size" +}; + void Control::accept_event() { if (is_inside_tree()) { get_viewport()->_gui_accept_event(); @@ -222,41 +237,42 @@ Transform2D Control::_get_internal_transform() const { bool Control::_set(const StringName &p_name, const Variant &p_value) { String name = p_name; - if (!name.begins_with("custom")) { + // Prefixes "custom_*" are supported for compatibility with 3.x. + if (!name.begins_with("theme_override") && !name.begins_with("custom")) { return false; } if (p_value.get_type() == Variant::NIL) { - if (name.begins_with("custom_icons/")) { + if (name.begins_with("theme_override_icons/") || name.begins_with("custom_icons/")) { String dname = name.get_slicec('/', 1); if (data.icon_override.has(dname)) { data.icon_override[dname]->disconnect("changed", callable_mp(this, &Control::_override_changed)); } data.icon_override.erase(dname); notification(NOTIFICATION_THEME_CHANGED); - } else if (name.begins_with("custom_styles/")) { + } else if (name.begins_with("theme_override_styles/") || name.begins_with("custom_styles/")) { String dname = name.get_slicec('/', 1); if (data.style_override.has(dname)) { data.style_override[dname]->disconnect("changed", callable_mp(this, &Control::_override_changed)); } data.style_override.erase(dname); notification(NOTIFICATION_THEME_CHANGED); - } else if (name.begins_with("custom_fonts/")) { + } else if (name.begins_with("theme_override_fonts/") || name.begins_with("custom_fonts/")) { String dname = name.get_slicec('/', 1); if (data.font_override.has(dname)) { data.font_override[dname]->disconnect("changed", callable_mp(this, &Control::_override_changed)); } data.font_override.erase(dname); notification(NOTIFICATION_THEME_CHANGED); - } else if (name.begins_with("custom_font_sizes/")) { + } else if (name.begins_with("theme_override_font_sizes/") || name.begins_with("custom_font_sizes/")) { String dname = name.get_slicec('/', 1); data.font_size_override.erase(dname); notification(NOTIFICATION_THEME_CHANGED); - } else if (name.begins_with("custom_colors/")) { + } else if (name.begins_with("theme_override_colors/") || name.begins_with("custom_colors/")) { String dname = name.get_slicec('/', 1); data.color_override.erase(dname); notification(NOTIFICATION_THEME_CHANGED); - } else if (name.begins_with("custom_constants/")) { + } else if (name.begins_with("theme_override_constants/") || name.begins_with("custom_constants/")) { String dname = name.get_slicec('/', 1); data.constant_override.erase(dname); notification(NOTIFICATION_THEME_CHANGED); @@ -265,22 +281,22 @@ bool Control::_set(const StringName &p_name, const Variant &p_value) { } } else { - if (name.begins_with("custom_icons/")) { + if (name.begins_with("theme_override_icons/") || name.begins_with("custom_icons/")) { String dname = name.get_slicec('/', 1); add_theme_icon_override(dname, p_value); - } else if (name.begins_with("custom_styles/")) { + } else if (name.begins_with("theme_override_styles/") || name.begins_with("custom_styles/")) { String dname = name.get_slicec('/', 1); add_theme_style_override(dname, p_value); - } else if (name.begins_with("custom_fonts/")) { + } else if (name.begins_with("theme_override_fonts/") || name.begins_with("custom_fonts/")) { String dname = name.get_slicec('/', 1); add_theme_font_override(dname, p_value); - } else if (name.begins_with("custom_font_sizes/")) { + } else if (name.begins_with("theme_override_font_sizes/") || name.begins_with("custom_font_sizes/")) { String dname = name.get_slicec('/', 1); add_theme_font_size_override(dname, p_value); - } else if (name.begins_with("custom_colors/")) { + } else if (name.begins_with("theme_override_colors/") || name.begins_with("custom_colors/")) { String dname = name.get_slicec('/', 1); add_theme_color_override(dname, p_value); - } else if (name.begins_with("custom_constants/")) { + } else if (name.begins_with("theme_override_constants/") || name.begins_with("custom_constants/")) { String dname = name.get_slicec('/', 1); add_theme_constant_override(dname, p_value); } else { @@ -307,27 +323,26 @@ void Control::_update_minimum_size() { bool Control::_get(const StringName &p_name, Variant &r_ret) const { String sname = p_name; - - if (!sname.begins_with("custom")) { + if (!sname.begins_with("theme_override")) { return false; } - if (sname.begins_with("custom_icons/")) { + if (sname.begins_with("theme_override_icons/")) { String name = sname.get_slicec('/', 1); r_ret = data.icon_override.has(name) ? Variant(data.icon_override[name]) : Variant(); - } else if (sname.begins_with("custom_styles/")) { + } else if (sname.begins_with("theme_override_styles/")) { String name = sname.get_slicec('/', 1); r_ret = data.style_override.has(name) ? Variant(data.style_override[name]) : Variant(); - } else if (sname.begins_with("custom_fonts/")) { + } else if (sname.begins_with("theme_override_fonts/")) { String name = sname.get_slicec('/', 1); r_ret = data.font_override.has(name) ? Variant(data.font_override[name]) : Variant(); - } else if (sname.begins_with("custom_font_sizes/")) { + } else if (sname.begins_with("theme_override_font_sizes/")) { String name = sname.get_slicec('/', 1); r_ret = data.font_size_override.has(name) ? Variant(data.font_size_override[name]) : Variant(); - } else if (sname.begins_with("custom_colors/")) { + } else if (sname.begins_with("theme_override_colors/")) { String name = sname.get_slicec('/', 1); r_ret = data.color_override.has(name) ? Variant(data.color_override[name]) : Variant(); - } else if (sname.begins_with("custom_constants/")) { + } else if (sname.begins_with("theme_override_constants/")) { String name = sname.get_slicec('/', 1); r_ret = data.constant_override.has(name) ? Variant(data.constant_override[name]) : Variant(); } else { @@ -339,96 +354,139 @@ bool Control::_get(const StringName &p_name, Variant &r_ret) const { void Control::_get_property_list(List<PropertyInfo> *p_list) const { Ref<Theme> theme = Theme::get_default(); - /* Using the default theme since the properties below are meant for editor only - if (data.theme.is_valid()) { - theme = data.theme; - } else { - theme = Theme::get_default(); - }*/ + p_list->push_back(PropertyInfo(Variant::NIL, "Theme Overrides", PROPERTY_HINT_NONE, "theme_override_", PROPERTY_USAGE_GROUP)); { List<StringName> names; - theme->get_icon_list(get_class_name(), &names); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + theme->get_color_list(get_class_name(), &names); + for (const StringName &E : names) { uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; - if (data.icon_override.has(E->get())) { + if (data.color_override.has(E)) { usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } - p_list->push_back(PropertyInfo(Variant::OBJECT, "custom_icons/" + E->get(), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", usage)); + p_list->push_back(PropertyInfo(Variant::COLOR, "theme_override_colors/" + E, PROPERTY_HINT_NONE, "", usage)); } } { List<StringName> names; - theme->get_stylebox_list(get_class_name(), &names); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + theme->get_constant_list(get_class_name(), &names); + for (const StringName &E : names) { uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; - if (data.style_override.has(E->get())) { + if (data.constant_override.has(E)) { usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } - p_list->push_back(PropertyInfo(Variant::OBJECT, "custom_styles/" + E->get(), PROPERTY_HINT_RESOURCE_TYPE, "StyleBox", usage)); + p_list->push_back(PropertyInfo(Variant::INT, "theme_override_constants/" + E, PROPERTY_HINT_RANGE, "-16384,16384", usage)); } } { List<StringName> names; theme->get_font_list(get_class_name(), &names); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + for (const StringName &E : names) { uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; - if (data.font_override.has(E->get())) { + if (data.font_override.has(E)) { usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } - p_list->push_back(PropertyInfo(Variant::OBJECT, "custom_fonts/" + E->get(), PROPERTY_HINT_RESOURCE_TYPE, "Font", usage)); + p_list->push_back(PropertyInfo(Variant::OBJECT, "theme_override_fonts/" + E, PROPERTY_HINT_RESOURCE_TYPE, "Font", usage)); } } { List<StringName> names; theme->get_font_size_list(get_class_name(), &names); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + for (const StringName &E : names) { uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; - if (data.font_size_override.has(E->get())) { + if (data.font_size_override.has(E)) { usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } - p_list->push_back(PropertyInfo(Variant::INT, "custom_font_sizes/" + E->get(), PROPERTY_HINT_NONE, "", usage)); + p_list->push_back(PropertyInfo(Variant::INT, "theme_override_font_sizes/" + E, PROPERTY_HINT_NONE, "", usage)); } } { List<StringName> names; - theme->get_color_list(get_class_name(), &names); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + theme->get_icon_list(get_class_name(), &names); + for (const StringName &E : names) { uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; - if (data.color_override.has(E->get())) { + if (data.icon_override.has(E)) { usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } - p_list->push_back(PropertyInfo(Variant::COLOR, "custom_colors/" + E->get(), PROPERTY_HINT_NONE, "", usage)); + p_list->push_back(PropertyInfo(Variant::OBJECT, "theme_override_icons/" + E, PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", usage)); } } { List<StringName> names; - theme->get_constant_list(get_class_name(), &names); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + theme->get_stylebox_list(get_class_name(), &names); + for (const StringName &E : names) { uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; - if (data.constant_override.has(E->get())) { + if (data.style_override.has(E)) { usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } - p_list->push_back(PropertyInfo(Variant::INT, "custom_constants/" + E->get(), PROPERTY_HINT_RANGE, "-16384,16384", usage)); + p_list->push_back(PropertyInfo(Variant::OBJECT, "theme_override_styles/" + E, PROPERTY_HINT_RESOURCE_TYPE, "StyleBox", usage)); } } } +void Control::_validate_property(PropertyInfo &property) const { + if (property.name == "theme_type_variation") { + List<StringName> names; + + // Only the default theme and the project theme are used for the list of options. + // This is an imposed limitation to simplify the logic needed to leverage those options. + Theme::get_default()->get_type_variation_list(get_class_name(), &names); + if (Theme::get_project_default().is_valid()) { + Theme::get_project_default()->get_type_variation_list(get_class_name(), &names); + } + names.sort_custom<StringName::AlphCompare>(); + + Vector<StringName> unique_names; + String hint_string; + for (const StringName &E : names) { + // Skip duplicate values. + if (unique_names.has(E)) { + continue; + } + + hint_string += String(E) + ","; + unique_names.append(E); + } + + property.hint_string = hint_string; + } + if (!Object::cast_to<Container>(get_parent())) { + return; + } + // Disable the property if it's managed by the parent container. + bool property_is_managed_by_container = false; + for (unsigned i = 0; i < properties_managed_by_container_count; i++) { + property_is_managed_by_container = properties_managed_by_container[i] == property.name; + if (property_is_managed_by_container) { + break; + } + } + if (property_is_managed_by_container) { + property.usage |= PROPERTY_USAGE_READ_ONLY; + } +} + Control *Control::get_parent_control() const { return data.parent; } +Window *Control::get_parent_window() const { + return data.parent_window; +} + void Control::set_layout_direction(Control::LayoutDirection p_direction) { ERR_FAIL_INDEX((int)p_direction, 4); data.layout_dir = p_direction; + data.is_rtl_dirty = true; + propagate_notification(NOTIFICATION_LAYOUT_DIRECTION_CHANGED); } @@ -437,29 +495,49 @@ Control::LayoutDirection Control::get_layout_direction() const { } bool Control::is_layout_rtl() const { - if (data.layout_dir == LAYOUT_DIRECTION_INHERITED) { - Window *parent_window = Object::cast_to<Window>(get_parent()); - Control *parent_control = get_parent_control(); - if (parent_control) { - return parent_control->is_layout_rtl(); - } else if (parent_window) { - return parent_window->is_layout_rtl(); - } else { - if (GLOBAL_GET("internationalization/rendering/force_right_to_left_layout_direction")) { - return true; + if (data.is_rtl_dirty) { + const_cast<Control *>(this)->data.is_rtl_dirty = false; + if (data.layout_dir == LAYOUT_DIRECTION_INHERITED) { + Window *parent_window = get_parent_window(); + Control *parent_control = get_parent_control(); + if (parent_control) { + const_cast<Control *>(this)->data.is_rtl = parent_control->is_layout_rtl(); + } else if (parent_window) { + const_cast<Control *>(this)->data.is_rtl = parent_window->is_layout_rtl(); + } else { + if (GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) { + const_cast<Control *>(this)->data.is_rtl = true; + } else { + String locale = TranslationServer::get_singleton()->get_tool_locale(); + const_cast<Control *>(this)->data.is_rtl = TS->is_locale_right_to_left(locale); + } } - String locale = TranslationServer::get_singleton()->get_tool_locale(); - return TS->is_locale_right_to_left(locale); - } - } else if (data.layout_dir == LAYOUT_DIRECTION_LOCALE) { - if (GLOBAL_GET("internationalization/rendering/force_right_to_left_layout_direction")) { - return true; + } else if (data.layout_dir == LAYOUT_DIRECTION_LOCALE) { + if (GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) { + const_cast<Control *>(this)->data.is_rtl = true; + } else { + String locale = TranslationServer::get_singleton()->get_tool_locale(); + const_cast<Control *>(this)->data.is_rtl = TS->is_locale_right_to_left(locale); + } + } else { + const_cast<Control *>(this)->data.is_rtl = (data.layout_dir == LAYOUT_DIRECTION_RTL); } - String locale = TranslationServer::get_singleton()->get_tool_locale(); - return TS->is_locale_right_to_left(locale); - } else { - return (data.layout_dir == LAYOUT_DIRECTION_RTL); } + return data.is_rtl; +} + +void Control::set_auto_translate(bool p_enable) { + if (p_enable == data.auto_translate) { + return; + } + + data.auto_translate = p_enable; + + notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); +} + +bool Control::is_auto_translating() const { + return data.auto_translate; } void Control::_clear_size_warning() { @@ -509,6 +587,7 @@ void Control::_notification(int p_notification) { } break; case NOTIFICATION_POST_ENTER_TREE: { data.minimum_size_valid = false; + data.is_rtl_dirty = true; _size_changed(); } break; case NOTIFICATION_EXIT_TREE: { @@ -522,6 +601,8 @@ void Control::_notification(int p_notification) { case NOTIFICATION_ENTER_CANVAS: { data.parent = Object::cast_to<Control>(get_parent()); + data.parent_window = Object::cast_to<Window>(get_parent()); + data.is_rtl_dirty = true; Node *parent = this; //meh Control *parent_control = nullptr; @@ -586,6 +667,8 @@ void Control::_notification(int p_notification) { data.parent = nullptr; data.parent_canvas_item = nullptr; + data.parent_window = nullptr; + data.is_rtl_dirty = true; } break; case NOTIFICATION_MOVED_IN_PARENT: { @@ -645,6 +728,7 @@ void Control::_notification(int p_notification) { } break; case NOTIFICATION_TRANSLATION_CHANGED: case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { + data.is_rtl_dirty = true; _size_changed(); } break; } @@ -675,14 +759,9 @@ Variant Control::get_drag_data(const Point2 &p_point) { } } - if (get_script_instance()) { - Variant v = p_point; - const Variant *p = &v; - Callable::CallError ce; - Variant ret = get_script_instance()->call(SceneStringNames::get_singleton()->_get_drag_data, &p, 1, ce); - if (ce.error == Callable::CallError::CALL_OK) { - return ret; - } + Variant dd; + if (GDVIRTUAL_CALL(_get_drag_data, p_point, dd)) { + return dd; } return Variant(); @@ -697,16 +776,10 @@ bool Control::can_drop_data(const Point2 &p_point, const Variant &p_data) const } } - if (get_script_instance()) { - Variant v = p_point; - const Variant *p[2] = { &v, &p_data }; - Callable::CallError ce; - Variant ret = get_script_instance()->call(SceneStringNames::get_singleton()->_can_drop_data, p, 2, ce); - if (ce.error == Callable::CallError::CALL_OK) { - return ret; - } + bool ret; + if (GDVIRTUAL_CALL(_can_drop_data, p_point, p_data, ret)) { + return ret; } - return false; } @@ -720,15 +793,7 @@ void Control::drop_data(const Point2 &p_point, const Variant &p_data) { } } - if (get_script_instance()) { - Variant v = p_point; - const Variant *p[2] = { &v, &p_data }; - Callable::CallError ce; - Variant ret = get_script_instance()->call(SceneStringNames::get_singleton()->_drop_data, p, 2, ce); - if (ce.error == Callable::CallError::CALL_OK) { - return; - } - } + GDVIRTUAL_CALL(_drop_data, p_point, p_data); } void Control::force_drag(const Variant &p_data, Control *p_control) { @@ -744,16 +809,26 @@ void Control::set_drag_preview(Control *p_control) { get_viewport()->_gui_set_drag_preview(this, p_control); } +void Control::_call_gui_input(const Ref<InputEvent> &p_event) { + emit_signal(SceneStringNames::get_singleton()->gui_input, p_event); //signal should be first, so it's possible to override an event (and then accept it) + if (!is_inside_tree() || get_viewport()->is_input_handled()) { + return; //input was handled, abort + } + GDVIRTUAL_CALL(_gui_input, p_event); + if (!is_inside_tree() || get_viewport()->is_input_handled()) { + return; //input was handled, abort + } + gui_input(p_event); +} +void Control::gui_input(const Ref<InputEvent> &p_event) { +} + Size2 Control::get_minimum_size() const { - ScriptInstance *si = const_cast<Control *>(this)->get_script_instance(); - if (si) { - Callable::CallError ce; - Variant s = si->call(SceneStringNames::get_singleton()->_get_minimum_size, nullptr, 0, ce); - if (ce.error == Callable::CallError::CALL_OK) { - return s; - } + Vector2 ms; + if (GDVIRTUAL_CALL(_get_minimum_size, ms)) { + return ms; } - return Size2(); + return Vector2(); } template <class T> @@ -766,13 +841,13 @@ T Control::get_theme_item_in_types(Control *p_theme_owner, Window *p_theme_owner Window *theme_owner_window = p_theme_owner_window; while (theme_owner || theme_owner_window) { - for (List<StringName>::Element *E = p_theme_types.front(); E; E = E->next()) { - if (theme_owner && theme_owner->data.theme->has_theme_item(p_data_type, p_name, E->get())) { - return theme_owner->data.theme->get_theme_item(p_data_type, p_name, E->get()); + for (const StringName &E : p_theme_types) { + if (theme_owner && theme_owner->data.theme->has_theme_item(p_data_type, p_name, E)) { + return theme_owner->data.theme->get_theme_item(p_data_type, p_name, E); } - if (theme_owner_window && theme_owner_window->theme->has_theme_item(p_data_type, p_name, E->get())) { - return theme_owner_window->theme->get_theme_item(p_data_type, p_name, E->get()); + if (theme_owner_window && theme_owner_window->theme->has_theme_item(p_data_type, p_name, E)) { + return theme_owner_window->theme->get_theme_item(p_data_type, p_name, E); } } @@ -795,17 +870,17 @@ T Control::get_theme_item_in_types(Control *p_theme_owner, Window *p_theme_owner // Secondly, check the project-defined Theme resource. if (Theme::get_project_default().is_valid()) { - for (List<StringName>::Element *E = p_theme_types.front(); E; E = E->next()) { - if (Theme::get_project_default()->has_theme_item(p_data_type, p_name, E->get())) { - return Theme::get_project_default()->get_theme_item(p_data_type, p_name, E->get()); + for (const StringName &E : p_theme_types) { + if (Theme::get_project_default()->has_theme_item(p_data_type, p_name, E)) { + return Theme::get_project_default()->get_theme_item(p_data_type, p_name, E); } } } // Lastly, fall back on the items defined in the default Theme, if they exist. - for (List<StringName>::Element *E = p_theme_types.front(); E; E = E->next()) { - if (Theme::get_default()->has_theme_item(p_data_type, p_name, E->get())) { - return Theme::get_default()->get_theme_item(p_data_type, p_name, E->get()); + for (const StringName &E : p_theme_types) { + if (Theme::get_default()->has_theme_item(p_data_type, p_name, E)) { + return Theme::get_default()->get_theme_item(p_data_type, p_name, E); } } // If they don't exist, use any type to return the default/empty value. @@ -821,12 +896,12 @@ bool Control::has_theme_item_in_types(Control *p_theme_owner, Window *p_theme_ow Window *theme_owner_window = p_theme_owner_window; while (theme_owner || theme_owner_window) { - for (List<StringName>::Element *E = p_theme_types.front(); E; E = E->next()) { - if (theme_owner && theme_owner->data.theme->has_theme_item(p_data_type, p_name, E->get())) { + for (const StringName &E : p_theme_types) { + if (theme_owner && theme_owner->data.theme->has_theme_item(p_data_type, p_name, E)) { return true; } - if (theme_owner_window && theme_owner_window->theme->has_theme_item(p_data_type, p_name, E->get())) { + if (theme_owner_window && theme_owner_window->theme->has_theme_item(p_data_type, p_name, E)) { return true; } } @@ -850,16 +925,16 @@ bool Control::has_theme_item_in_types(Control *p_theme_owner, Window *p_theme_ow // Secondly, check the project-defined Theme resource. if (Theme::get_project_default().is_valid()) { - for (List<StringName>::Element *E = p_theme_types.front(); E; E = E->next()) { - if (Theme::get_project_default()->has_theme_item(p_data_type, p_name, E->get())) { + for (const StringName &E : p_theme_types) { + if (Theme::get_project_default()->has_theme_item(p_data_type, p_name, E)) { return true; } } } // Lastly, fall back on the items defined in the default Theme, if they exist. - for (List<StringName>::Element *E = p_theme_types.front(); E; E = E->next()) { - if (Theme::get_default()->has_theme_item(p_data_type, p_name, E->get())) { + for (const StringName &E : p_theme_types) { + if (Theme::get_default()->has_theme_item(p_data_type, p_name, E)) { return true; } } @@ -867,18 +942,19 @@ bool Control::has_theme_item_in_types(Control *p_theme_owner, Window *p_theme_ow } void Control::_get_theme_type_dependencies(const StringName &p_theme_type, List<StringName> *p_list) const { - if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_custom_type) { - if (data.theme_custom_type != StringName()) { - p_list->push_back(data.theme_custom_type); + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { + if (Theme::get_project_default().is_valid() && Theme::get_project_default()->get_type_variation_base(data.theme_type_variation) != StringName()) { + Theme::get_project_default()->get_type_dependencies(get_class_name(), data.theme_type_variation, p_list); + } else { + Theme::get_default()->get_type_dependencies(get_class_name(), data.theme_type_variation, p_list); } - Theme::get_type_dependencies(get_class_name(), p_list); } else { - Theme::get_type_dependencies(p_theme_type, p_list); + Theme::get_default()->get_type_dependencies(p_theme_type, StringName(), p_list); } } Ref<Texture2D> Control::get_theme_icon(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_custom_type) { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { const Ref<Texture2D> *tex = data.icon_override.getptr(p_name); if (tex) { return *tex; @@ -891,7 +967,7 @@ Ref<Texture2D> Control::get_theme_icon(const StringName &p_name, const StringNam } Ref<StyleBox> Control::get_theme_stylebox(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_custom_type) { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { const Ref<StyleBox> *style = data.style_override.getptr(p_name); if (style) { return *style; @@ -904,7 +980,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_custom_type) { + 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) { return *font; @@ -917,7 +993,7 @@ Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_ } int Control::get_theme_font_size(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_custom_type) { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { const int *font_size = data.font_size_override.getptr(p_name); if (font_size) { return *font_size; @@ -930,7 +1006,7 @@ int Control::get_theme_font_size(const StringName &p_name, const StringName &p_t } Color Control::get_theme_color(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_custom_type) { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { const Color *color = data.color_override.getptr(p_name); if (color) { return *color; @@ -943,7 +1019,7 @@ Color Control::get_theme_color(const StringName &p_name, const StringName &p_the } int Control::get_theme_constant(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_custom_type) { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { const int *constant = data.constant_override.getptr(p_name); if (constant) { return *constant; @@ -986,7 +1062,7 @@ bool Control::has_theme_constant_override(const StringName &p_name) const { } bool Control::has_theme_icon(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_custom_type) { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { if (has_theme_icon_override(p_name)) { return true; } @@ -998,7 +1074,7 @@ bool Control::has_theme_icon(const StringName &p_name, const StringName &p_theme } bool Control::has_theme_stylebox(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_custom_type) { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { if (has_theme_stylebox_override(p_name)) { return true; } @@ -1010,7 +1086,7 @@ bool Control::has_theme_stylebox(const StringName &p_name, const StringName &p_t } bool Control::has_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_custom_type) { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { if (has_theme_font_override(p_name)) { return true; } @@ -1022,7 +1098,7 @@ bool Control::has_theme_font(const StringName &p_name, const StringName &p_theme } bool Control::has_theme_font_size(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_custom_type) { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { if (has_theme_font_size_override(p_name)) { return true; } @@ -1034,7 +1110,7 @@ bool Control::has_theme_font_size(const StringName &p_name, const StringName &p_ } bool Control::has_theme_color(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_custom_type) { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { if (has_theme_color_override(p_name)) { return true; } @@ -1046,7 +1122,7 @@ bool Control::has_theme_color(const StringName &p_name, const StringName &p_them } bool Control::has_theme_constant(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_custom_type) { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { if (has_theme_constant_override(p_name)) { return true; } @@ -1566,7 +1642,7 @@ void Control::set_rect(const Rect2 &p_rect) { void Control::_set_size(const Size2 &p_size) { #ifdef DEBUG_ENABLED if (data.size_warning && (data.anchor[SIDE_LEFT] != data.anchor[SIDE_RIGHT] || data.anchor[SIDE_TOP] != data.anchor[SIDE_BOTTOM])) { - WARN_PRINT("Nodes with non-equal opposite anchors will have their size overriden after _ready(). \nIf you want to set size, change the anchors or consider using set_deferred()."); + WARN_PRINT("Nodes with non-equal opposite anchors will have their size overridden after _ready(). \nIf you want to set size, change the anchors or consider using set_deferred()."); } #endif set_size(p_size); @@ -1630,6 +1706,17 @@ Rect2 Control::get_anchorable_rect() const { return Rect2(Point2(), get_size()); } +void Control::begin_bulk_theme_override() { + data.bulk_theme_override = true; +} + +void Control::end_bulk_theme_override() { + ERR_FAIL_COND(!data.bulk_theme_override); + + data.bulk_theme_override = false; + _notify_theme_changed(); +} + void Control::add_theme_icon_override(const StringName &p_name, const Ref<Texture2D> &p_icon) { ERR_FAIL_COND(!p_icon.is_valid()); @@ -1639,7 +1726,7 @@ void Control::add_theme_icon_override(const StringName &p_name, const Ref<Textur data.icon_override[p_name] = p_icon; data.icon_override[p_name]->connect("changed", callable_mp(this, &Control::_override_changed), Vector<Variant>(), CONNECT_REFERENCE_COUNTED); - notification(NOTIFICATION_THEME_CHANGED); + _notify_theme_changed(); } void Control::add_theme_style_override(const StringName &p_name, const Ref<StyleBox> &p_style) { @@ -1651,7 +1738,7 @@ void Control::add_theme_style_override(const StringName &p_name, const Ref<Style data.style_override[p_name] = p_style; data.style_override[p_name]->connect("changed", callable_mp(this, &Control::_override_changed), Vector<Variant>(), CONNECT_REFERENCE_COUNTED); - notification(NOTIFICATION_THEME_CHANGED); + _notify_theme_changed(); } void Control::add_theme_font_override(const StringName &p_name, const Ref<Font> &p_font) { @@ -1663,22 +1750,22 @@ void Control::add_theme_font_override(const StringName &p_name, const Ref<Font> data.font_override[p_name] = p_font; data.font_override[p_name]->connect("changed", callable_mp(this, &Control::_override_changed), Vector<Variant>(), CONNECT_REFERENCE_COUNTED); - notification(NOTIFICATION_THEME_CHANGED); + _notify_theme_changed(); } void Control::add_theme_font_size_override(const StringName &p_name, int p_font_size) { data.font_size_override[p_name] = p_font_size; - notification(NOTIFICATION_THEME_CHANGED); + _notify_theme_changed(); } void Control::add_theme_color_override(const StringName &p_name, const Color &p_color) { data.color_override[p_name] = p_color; - notification(NOTIFICATION_THEME_CHANGED); + _notify_theme_changed(); } void Control::add_theme_constant_override(const StringName &p_name, int p_constant) { data.constant_override[p_name] = p_constant; - notification(NOTIFICATION_THEME_CHANGED); + _notify_theme_changed(); } void Control::remove_theme_icon_override(const StringName &p_name) { @@ -1687,7 +1774,7 @@ void Control::remove_theme_icon_override(const StringName &p_name) { } data.icon_override.erase(p_name); - notification(NOTIFICATION_THEME_CHANGED); + _notify_theme_changed(); } void Control::remove_theme_style_override(const StringName &p_name) { @@ -1696,7 +1783,7 @@ void Control::remove_theme_style_override(const StringName &p_name) { } data.style_override.erase(p_name); - notification(NOTIFICATION_THEME_CHANGED); + _notify_theme_changed(); } void Control::remove_theme_font_override(const StringName &p_name) { @@ -1705,22 +1792,22 @@ void Control::remove_theme_font_override(const StringName &p_name) { } data.font_override.erase(p_name); - notification(NOTIFICATION_THEME_CHANGED); + _notify_theme_changed(); } void Control::remove_theme_font_size_override(const StringName &p_name) { data.font_size_override.erase(p_name); - notification(NOTIFICATION_THEME_CHANGED); + _notify_theme_changed(); } void Control::remove_theme_color_override(const StringName &p_name) { data.color_override.erase(p_name); - notification(NOTIFICATION_THEME_CHANGED); + _notify_theme_changed(); } void Control::remove_theme_constant_override(const StringName &p_name) { data.constant_override.erase(p_name); - notification(NOTIFICATION_THEME_CHANGED); + _notify_theme_changed(); } void Control::set_focus_mode(FocusMode p_focus_mode) { @@ -1993,6 +2080,12 @@ void Control::_theme_changed() { _propagate_theme_changed(this, this, nullptr, false); } +void Control::_notify_theme_changed() { + if (!data.bulk_theme_override) { + notification(NOTIFICATION_THEME_CHANGED); + } +} + void Control::set_theme(const Ref<Theme> &p_theme) { if (data.theme == p_theme) { return; @@ -2031,13 +2124,13 @@ Ref<Theme> Control::get_theme() const { return data.theme; } -void Control::set_theme_custom_type(const StringName &p_theme_type) { - data.theme_custom_type = p_theme_type; +void Control::set_theme_type_variation(const StringName &p_theme_type) { + data.theme_type_variation = p_theme_type; _propagate_theme_changed(this, data.theme_owner, data.theme_owner_window); } -StringName Control::get_theme_custom_type() const { - return data.theme_custom_type; +StringName Control::get_theme_type_variation() const { + return data.theme_type_variation; } void Control::set_tooltip(const String &p_tooltip) { @@ -2050,8 +2143,9 @@ String Control::get_tooltip(const Point2 &p_pos) const { } Control *Control::make_custom_tooltip(const String &p_text) const { - if (get_script_instance()) { - return const_cast<Control *>(this)->call("_make_custom_tooltip", p_text); + Object *ret = nullptr; + if (GDVIRTUAL_CALL(_make_custom_tooltip, p_text, ret)) { + return Object::cast_to<Control>(ret); } return nullptr; } @@ -2426,14 +2520,11 @@ Vector<Vector2i> Control::structured_text_parser(StructuredTextParser p_theme_ty } } break; case STRUCTURED_TEXT_CUSTOM: { - if (get_script_instance()) { - Variant data = get_script_instance()->call(SceneStringNames::get_singleton()->_structured_text_parser, p_args, p_text); - if (data.get_type() == Variant::ARRAY) { - Array _data = data; - for (int i = 0; i < _data.size(); i++) { - if (_data[i].get_type() == Variant::VECTOR2I) { - ret.push_back(Vector2i(_data[i])); - } + 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])); } } } @@ -2529,7 +2620,7 @@ bool Control::is_visibility_clip_disabled() const { void Control::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { #ifdef TOOLS_ENABLED - const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", 0) ? "'" : "\""; + const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\""; #else const String quote_style = "\""; #endif @@ -2552,8 +2643,8 @@ void Control::get_argument_options(const StringName &p_function, int p_idx, List } sn.sort_custom<StringName::AlphCompare>(); - for (List<StringName>::Element *E = sn.front(); E; E = E->next()) { - r_options->push_back(quote_style + E->get() + quote_style); + for (const StringName &name : sn) { + r_options->push_back(String(name).quote(quote_style)); } } } @@ -2660,8 +2751,11 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("set_theme", "theme"), &Control::set_theme); ClassDB::bind_method(D_METHOD("get_theme"), &Control::get_theme); - ClassDB::bind_method(D_METHOD("set_theme_custom_type", "theme_type"), &Control::set_theme_custom_type); - ClassDB::bind_method(D_METHOD("get_theme_custom_type"), &Control::get_theme_custom_type); + ClassDB::bind_method(D_METHOD("set_theme_type_variation", "theme_type"), &Control::set_theme_type_variation); + ClassDB::bind_method(D_METHOD("get_theme_type_variation"), &Control::get_theme_type_variation); + + ClassDB::bind_method(D_METHOD("begin_bulk_theme_override"), &Control::begin_bulk_theme_override); + ClassDB::bind_method(D_METHOD("end_bulk_theme_override"), &Control::end_bulk_theme_override); ClassDB::bind_method(D_METHOD("add_theme_icon_override", "name", "texture"), &Control::add_theme_icon_override); ClassDB::bind_method(D_METHOD("add_theme_stylebox_override", "name", "stylebox"), &Control::add_theme_style_override); @@ -2744,20 +2838,8 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("get_layout_direction"), &Control::get_layout_direction); ClassDB::bind_method(D_METHOD("is_layout_rtl"), &Control::is_layout_rtl); - BIND_VMETHOD(MethodInfo("_structured_text_parser", PropertyInfo(Variant::ARRAY, "args"), PropertyInfo(Variant::STRING, "text"))); - - BIND_VMETHOD(MethodInfo("_gui_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"))); - BIND_VMETHOD(MethodInfo(Variant::VECTOR2, "_get_minimum_size")); - - MethodInfo get_drag_data = MethodInfo("_get_drag_data", PropertyInfo(Variant::VECTOR2, "position")); - get_drag_data.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - BIND_VMETHOD(get_drag_data); - - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_can_drop_data", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::NIL, "data"))); - BIND_VMETHOD(MethodInfo("_drop_data", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::NIL, "data"))); - BIND_VMETHOD(MethodInfo( - PropertyInfo(Variant::OBJECT, "control", PROPERTY_HINT_RESOURCE_TYPE, "Control"), - "_make_custom_tooltip", PropertyInfo(Variant::STRING, "for_text"))); + ClassDB::bind_method(D_METHOD("set_auto_translate", "enable"), &Control::set_auto_translate); + ClassDB::bind_method(D_METHOD("is_auto_translating"), &Control::is_auto_translating); ADD_GROUP("Anchor", "anchor_"); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anchor_left", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", SIDE_LEFT); @@ -2778,6 +2860,9 @@ void Control::_bind_methods() { ADD_GROUP("Layout Direction", "layout_"); 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_GROUP("Auto Translate", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_translate"), "set_auto_translate", "is_auto_translating"); + ADD_GROUP("Rect", "rect_"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_position", "get_position"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_global_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "_set_global_position", "get_global_position"); @@ -2808,10 +2893,10 @@ void Control::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "size_flags_horizontal", PROPERTY_HINT_FLAGS, "Fill,Expand,Shrink Center,Shrink End"), "set_h_size_flags", "get_h_size_flags"); ADD_PROPERTY(PropertyInfo(Variant::INT, "size_flags_vertical", PROPERTY_HINT_FLAGS, "Fill,Expand,Shrink Center,Shrink End"), "set_v_size_flags", "get_v_size_flags"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "size_flags_stretch_ratio", PROPERTY_HINT_RANGE, "0,20,0.01,or_greater"), "set_stretch_ratio", "get_stretch_ratio"); + ADD_GROUP("Theme", "theme_"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "theme", PROPERTY_HINT_RESOURCE_TYPE, "Theme"), "set_theme", "get_theme"); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "theme_custom_type"), "set_theme_custom_type", "get_theme_custom_type"); - ADD_GROUP("", ""); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "theme_type_variation", PROPERTY_HINT_ENUM_SUGGESTION), "set_theme_type_variation", "get_theme_type_variation"); BIND_ENUM_CONSTANT(FOCUS_NONE); BIND_ENUM_CONSTANT(FOCUS_CLICK); @@ -2912,5 +2997,14 @@ void Control::_bind_methods() { ADD_SIGNAL(MethodInfo("minimum_size_changed")); ADD_SIGNAL(MethodInfo("theme_changed")); - GDVIRTUAL_BIND(_has_point); + GDVIRTUAL_BIND(_has_point, "position"); + GDVIRTUAL_BIND(_structured_text_parser, "args", "text"); + GDVIRTUAL_BIND(_get_minimum_size); + + GDVIRTUAL_BIND(_get_drag_data, "at_position"); + GDVIRTUAL_BIND(_can_drop_data, "at_position", "data"); + GDVIRTUAL_BIND(_drop_data, "at_position", "data"); + GDVIRTUAL_BIND(_make_custom_tooltip, "for_text"); + + GDVIRTUAL_BIND(_gui_input, "event"); } diff --git a/scene/gui/control.h b/scene/gui/control.h index 0642686a9f..9cec5d6e8d 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -31,10 +31,10 @@ #ifndef CONTROL_H #define CONTROL_H +#include "core/input/shortcut.h" #include "core/math/transform_2d.h" #include "core/object/gdvirtual.gen.inc" #include "core/templates/rid.h" -#include "scene/gui/shortcut.h" #include "scene/main/canvas_item.h" #include "scene/main/node.h" #include "scene/main/timer.h" @@ -179,6 +179,10 @@ private: GrowDirection v_grow = GROW_DIRECTION_END; LayoutDirection layout_dir = LAYOUT_DIRECTION_INHERITED; + bool is_rtl_dirty = true; + bool is_rtl = false; + + bool auto_translate = true; real_t rotation = 0.0; Vector2 scale = Vector2(1, 1); @@ -202,7 +206,8 @@ private: Ref<Theme> theme; Control *theme_owner = nullptr; Window *theme_owner_window = nullptr; - StringName theme_custom_type; + Window *parent_window = nullptr; + StringName theme_type_variation; String tooltip; CursorShape default_cursor = CURSOR_ARROW; @@ -215,6 +220,7 @@ private: NodePath focus_next; 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; @@ -224,6 +230,9 @@ private: } data; + static constexpr unsigned properties_managed_by_container_count = 11; + static String properties_managed_by_container[properties_managed_by_container_count]; + // used internally Control *_find_control_at_pos(CanvasItem *p_node, const Point2 &p_pos, const Transform2D &p_xform, Transform2D &r_inv_xform); @@ -236,6 +245,7 @@ private: void _set_size(const Size2 &p_size); void _theme_changed(); + void _notify_theme_changed(); void _update_minimum_size(); @@ -256,6 +266,8 @@ private: friend class Viewport; + void _call_gui_input(const Ref<InputEvent> &p_event); + void _update_minimum_size_cache(); friend class Window; static void _propagate_theme_changed(Node *p_at, Control *p_owner, Window *p_owner_window, bool p_assign = true); @@ -265,7 +277,6 @@ private: static bool has_theme_item_in_types(Control *p_theme_owner, Window *p_theme_owner_window, Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types); _FORCE_INLINE_ void _get_theme_type_dependencies(const StringName &p_theme_type, List<StringName> *p_list) const; - GDVIRTUAL1RC(bool, _has_point, Vector2) protected: virtual void add_child_notify(Node *p_child) override; virtual void remove_child_notify(Node *p_child) override; @@ -279,11 +290,22 @@ protected: void _get_property_list(List<PropertyInfo> *p_list) const; void _notification(int p_notification); - static void _bind_methods(); + virtual void _validate_property(PropertyInfo &property) const override; //bind helpers + GDVIRTUAL1RC(bool, _has_point, Vector2) + GDVIRTUAL2RC(Array, _structured_text_parser, Array, String) + GDVIRTUAL0RC(Vector2, _get_minimum_size) + + GDVIRTUAL1RC(Variant, _get_drag_data, Vector2) + GDVIRTUAL2RC(bool, _can_drop_data, Vector2, Variant) + GDVIRTUAL2(_drop_data, Vector2, Variant) + GDVIRTUAL1RC(Object *, _make_custom_tooltip, String) + + GDVIRTUAL1(_gui_input, Ref<InputEvent>) + public: enum { /* NOTIFICATION_DRAW=30, @@ -326,6 +348,8 @@ public: virtual Size2 _edit_get_minimum_size() const override; #endif + virtual void gui_input(const Ref<InputEvent> &p_event); + void accept_event(); virtual Size2 get_minimum_size() const; @@ -342,11 +366,16 @@ public: Size2 get_custom_minimum_size() const; Control *get_parent_control() const; + Window *get_parent_window() const; void set_layout_direction(LayoutDirection p_direction); LayoutDirection get_layout_direction() const; virtual bool is_layout_rtl() const; + void set_auto_translate(bool p_enable); + bool is_auto_translating() const; + _FORCE_INLINE_ String atr(const String p_string) const { return is_auto_translating() ? tr(p_string) : p_string; }; + /* POSITIONING */ void set_anchors_preset(LayoutPreset p_preset, bool p_keep_offsets = true); @@ -402,8 +431,8 @@ public: void set_theme(const Ref<Theme> &p_theme); Ref<Theme> get_theme() const; - void set_theme_custom_type(const StringName &p_theme_type); - StringName get_theme_custom_type() const; + void set_theme_type_variation(const StringName &p_theme_type); + StringName get_theme_type_variation() const; void set_h_size_flags(int p_flags); int get_h_size_flags() const; @@ -442,6 +471,9 @@ public: /* SKINNING */ + void begin_bulk_theme_override(); + void end_bulk_theme_override(); + void add_theme_icon_override(const StringName &p_name, const Ref<Texture2D> &p_icon); void add_theme_style_override(const StringName &p_name, const Ref<StyleBox> &p_style); void add_theme_font_override(const StringName &p_name, const Ref<Font> &p_font); diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index f63ae7569f..5d98aaa698 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -72,13 +72,10 @@ void AcceptDialog::_notification(int p_what) { parent_visible = nullptr; } } - } break; - case NOTIFICATION_THEME_CHANGED: { - bg->add_theme_style_override("panel", bg->get_theme_stylebox("panel", "AcceptDialog")); + bg->add_theme_style_override("panel", bg->get_theme_stylebox(SNAME("panel"), SNAME("AcceptDialog"))); } break; - case NOTIFICATION_EXIT_TREE: { if (parent_visible) { parent_visible->disconnect("focus_entered", callable_mp(this, &AcceptDialog::_parent_focused)); @@ -106,7 +103,7 @@ void AcceptDialog::_ok_pressed() { set_visible(false); } ok_pressed(); - emit_signal("confirmed"); + emit_signal(SNAME("confirmed")); } void AcceptDialog::_cancel_pressed() { @@ -116,9 +113,9 @@ void AcceptDialog::_cancel_pressed() { parent_visible = nullptr; } - call_deferred("hide"); + call_deferred(SNAME("hide")); - emit_signal("cancelled"); + emit_signal(SNAME("cancelled")); cancel_pressed(); @@ -148,11 +145,11 @@ bool AcceptDialog::get_hide_on_ok() const { } void AcceptDialog::set_autowrap(bool p_autowrap) { - label->set_autowrap(p_autowrap); + label->set_autowrap_mode(p_autowrap ? Label::AUTOWRAP_WORD : Label::AUTOWRAP_OFF); } bool AcceptDialog::has_autowrap() { - return label->has_autowrap(); + return label->get_autowrap_mode() != Label::AUTOWRAP_OFF; } void AcceptDialog::register_text_enter(Control *p_line_edit) { @@ -168,7 +165,7 @@ void AcceptDialog::_update_child_rects() { if (label->get_text().is_empty()) { label_size.height = 0; } - int margin = hbc->get_theme_constant("margin", "Dialogs"); + int margin = hbc->get_theme_constant(SNAME("margin"), SNAME("Dialogs")); Size2 size = get_size(); Size2 hminsize = hbc->get_combined_minimum_size(); @@ -200,7 +197,7 @@ void AcceptDialog::_update_child_rects() { } Size2 AcceptDialog::_get_contents_minimum_size() const { - int margin = hbc->get_theme_constant("margin", "Dialogs"); + int margin = hbc->get_theme_constant(SNAME("margin"), SNAME("Dialogs")); Size2 minsize = label->get_combined_minimum_size(); for (int i = 0; i < get_child_count(); i++) { @@ -230,7 +227,7 @@ Size2 AcceptDialog::_get_contents_minimum_size() const { } void AcceptDialog::_custom_action(const String &p_action) { - emit_signal("custom_action", p_action); + emit_signal(SNAME("custom_action"), p_action); custom_action(p_action); } @@ -263,6 +260,28 @@ Button *AcceptDialog::add_cancel_button(const String &p_cancel) { return b; } +void AcceptDialog::remove_button(Control *p_button) { + Button *button = Object::cast_to<Button>(p_button); + ERR_FAIL_NULL(button); + ERR_FAIL_COND_MSG(button->get_parent() != hbc, vformat("Cannot remove button %s as it does not belong to this dialog.", button->get_name())); + ERR_FAIL_COND_MSG(button == ok, "Cannot remove dialog's OK button."); + + Node *right_spacer = hbc->get_child(button->get_index() + 1); + // Should always be valid but let's avoid crashing + if (right_spacer) { + hbc->remove_child(right_spacer); + memdelete(right_spacer); + } + hbc->remove_child(button); + + if (button->is_connected("pressed", callable_mp(this, &AcceptDialog::_custom_action))) { + button->disconnect("pressed", callable_mp(this, &AcceptDialog::_custom_action)); + } + if (button->is_connected("pressed", callable_mp(this, &AcceptDialog::_cancel_pressed))) { + button->disconnect("pressed", callable_mp(this, &AcceptDialog::_cancel_pressed)); + } +} + void AcceptDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("get_ok_button"), &AcceptDialog::get_ok_button); ClassDB::bind_method(D_METHOD("get_label"), &AcceptDialog::get_label); @@ -270,6 +289,7 @@ void AcceptDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("get_hide_on_ok"), &AcceptDialog::get_hide_on_ok); ClassDB::bind_method(D_METHOD("add_button", "text", "right", "action"), &AcceptDialog::add_button, DEFVAL(false), DEFVAL("")); ClassDB::bind_method(D_METHOD("add_cancel_button", "name"), &AcceptDialog::add_cancel_button); + ClassDB::bind_method(D_METHOD("remove_button", "button"), &AcceptDialog::remove_button); ClassDB::bind_method(D_METHOD("register_text_enter", "line_edit"), &AcceptDialog::register_text_enter); ClassDB::bind_method(D_METHOD("set_text", "text"), &AcceptDialog::set_text); ClassDB::bind_method(D_METHOD("get_text"), &AcceptDialog::get_text); @@ -299,21 +319,21 @@ AcceptDialog::AcceptDialog() { set_clamp_to_embedder(true); bg = memnew(Panel); - add_child(bg); + add_child(bg, false, INTERNAL_MODE_FRONT); hbc = memnew(HBoxContainer); - int margin = hbc->get_theme_constant("margin", "Dialogs"); - int button_margin = hbc->get_theme_constant("button_margin", "Dialogs"); + int margin = hbc->get_theme_constant(SNAME("margin"), SNAME("Dialogs")); + int button_margin = hbc->get_theme_constant(SNAME("button_margin"), SNAME("Dialogs")); label = memnew(Label); label->set_anchor(SIDE_RIGHT, Control::ANCHOR_END); label->set_anchor(SIDE_BOTTOM, Control::ANCHOR_END); label->set_begin(Point2(margin, margin)); label->set_end(Point2(-margin, -button_margin - 10)); - add_child(label); + add_child(label, false, INTERNAL_MODE_FRONT); - add_child(hbc); + add_child(hbc, false, INTERNAL_MODE_FRONT); hbc->add_spacer(); ok = memnew(Button); diff --git a/scene/gui/dialogs.h b/scene/gui/dialogs.h index d389806fff..8e803a2a7c 100644 --- a/scene/gui/dialogs.h +++ b/scene/gui/dialogs.h @@ -82,6 +82,7 @@ public: Button *get_ok_button() { return ok; } Button *add_button(const String &p_text, bool p_right = false, const String &p_action = ""); Button *add_cancel_button(const String &p_cancel = ""); + void remove_button(Control *p_button); void set_hide_on_ok(bool p_hide); bool get_hide_on_ok() const; diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index f8cee6daec..973b72973d 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -49,9 +49,9 @@ VBoxContainer *FileDialog::get_vbox() { } void FileDialog::_theme_changed() { - Color font_color = vbox->get_theme_color("font_color", "Button"); - Color font_hover_color = vbox->get_theme_color("font_hover_color", "Button"); - Color font_pressed_color = vbox->get_theme_color("font_pressed_color", "Button"); + Color font_color = vbox->get_theme_color(SNAME("font_color"), SNAME("Button")); + Color font_hover_color = vbox->get_theme_color(SNAME("font_hover_color"), SNAME("Button")); + Color font_pressed_color = vbox->get_theme_color(SNAME("font_pressed_color"), SNAME("Button")); dir_up->add_theme_color_override("icon_normal_color", font_color); dir_up->add_theme_color_override("icon_hover_color", font_hover_color); @@ -81,21 +81,21 @@ void FileDialog::_notification(int p_what) { } } if (p_what == NOTIFICATION_ENTER_TREE) { - dir_up->set_icon(vbox->get_theme_icon("parent_folder", "FileDialog")); + dir_up->set_icon(vbox->get_theme_icon(SNAME("parent_folder"), SNAME("FileDialog"))); if (vbox->is_layout_rtl()) { - dir_prev->set_icon(vbox->get_theme_icon("forward_folder", "FileDialog")); - dir_next->set_icon(vbox->get_theme_icon("back_folder", "FileDialog")); + dir_prev->set_icon(vbox->get_theme_icon(SNAME("forward_folder"), SNAME("FileDialog"))); + dir_next->set_icon(vbox->get_theme_icon(SNAME("back_folder"), SNAME("FileDialog"))); } else { - dir_prev->set_icon(vbox->get_theme_icon("back_folder", "FileDialog")); - dir_next->set_icon(vbox->get_theme_icon("forward_folder", "FileDialog")); + dir_prev->set_icon(vbox->get_theme_icon(SNAME("back_folder"), SNAME("FileDialog"))); + dir_next->set_icon(vbox->get_theme_icon(SNAME("forward_folder"), SNAME("FileDialog"))); } - refresh->set_icon(vbox->get_theme_icon("reload", "FileDialog")); - show_hidden->set_icon(vbox->get_theme_icon("toggle_hidden", "FileDialog")); + refresh->set_icon(vbox->get_theme_icon(SNAME("reload"), SNAME("FileDialog"))); + show_hidden->set_icon(vbox->get_theme_icon(SNAME("toggle_hidden"), SNAME("FileDialog"))); _theme_changed(); } } -void FileDialog::_unhandled_input(const Ref<InputEvent> &p_event) { +void FileDialog::unhandled_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventKey> k = p_event; @@ -170,7 +170,7 @@ void FileDialog::_file_submitted(const String &p_file) { void FileDialog::_save_confirm_pressed() { String f = dir_access->get_current_dir().plus_file(file->get_text()); - emit_signal("file_selected", f); + emit_signal(SNAME("file_selected"), f); hide(); } @@ -224,7 +224,7 @@ void FileDialog::_action_pressed() { } if (files.size()) { - emit_signal("files_selected", files); + emit_signal(SNAME("files_selected"), files); hide(); } @@ -234,7 +234,7 @@ void FileDialog::_action_pressed() { String f = dir_access->get_current_dir().plus_file(file->get_text()); if ((mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_FILE) && dir_access->file_exists(f)) { - emit_signal("file_selected", f); + emit_signal(SNAME("file_selected"), f); hide(); } else if (mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_DIR) { String path = dir_access->get_current_dir(); @@ -248,7 +248,7 @@ void FileDialog::_action_pressed() { } } - emit_signal("dir_selected", path); + emit_signal(SNAME("dir_selected"), path); hide(); } @@ -308,7 +308,7 @@ void FileDialog::_action_pressed() { confirm_save->set_text(TTRC("File exists, overwrite?")); confirm_save->popup_centered(Size2(200, 80)); } else { - emit_signal("file_selected", f); + emit_signal(SNAME("file_selected"), f); hide(); } } @@ -437,8 +437,8 @@ void FileDialog::_tree_item_activated() { 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("_update_file_list"); - call_deferred("_update_dir"); + call_deferred(SNAME("_update_file_list")); + call_deferred(SNAME("_update_dir")); _push_history(); } else { _action_pressed(); @@ -468,10 +468,10 @@ void FileDialog::update_file_list() { dir_access->list_dir_begin(); TreeItem *root = tree->create_item(); - Ref<Texture2D> folder = vbox->get_theme_icon("folder", "FileDialog"); - Ref<Texture2D> file_icon = vbox->get_theme_icon("file", "FileDialog"); - const Color folder_color = vbox->get_theme_color("folder_icon_modulate", "FileDialog"); - const Color file_color = vbox->get_theme_color("file_icon_modulate", "FileDialog"); + Ref<Texture2D> folder = vbox->get_theme_icon(SNAME("folder"), SNAME("FileDialog")); + Ref<Texture2D> file_icon = vbox->get_theme_icon(SNAME("file"), SNAME("FileDialog")); + const Color folder_color = vbox->get_theme_color(SNAME("folder_icon_modulate"), SNAME("FileDialog")); + const Color file_color = vbox->get_theme_color(SNAME("file_icon_modulate"), SNAME("FileDialog")); List<String> files; List<String> dirs; @@ -552,9 +552,9 @@ void FileDialog::update_file_list() { bool match = patterns.is_empty(); String match_str; - for (List<String>::Element *E = patterns.front(); E; E = E->next()) { - if (files.front()->get().matchn(E->get())) { - match_str = E->get(); + for (const String &E : patterns) { + if (files.front()->get().matchn(E)) { + match_str = E; match = true; break; } @@ -573,7 +573,7 @@ void FileDialog::update_file_list() { ti->set_icon_modulate(0, file_color); if (mode == FILE_MODE_OPEN_DIR) { - ti->set_custom_color(0, vbox->get_theme_color("files_disabled", "FileDialog")); + ti->set_custom_color(0, vbox->get_theme_color(SNAME("files_disabled"), SNAME("FileDialog"))); ti->set_selectable(0, false); } Dictionary d; @@ -854,8 +854,6 @@ void FileDialog::_update_drives() { bool FileDialog::default_show_hidden_files = false; void FileDialog::_bind_methods() { - ClassDB::bind_method(D_METHOD("_unhandled_input"), &FileDialog::_unhandled_input); - ClassDB::bind_method(D_METHOD("_cancel_pressed"), &FileDialog::_cancel_pressed); ClassDB::bind_method(D_METHOD("clear_filters"), &FileDialog::clear_filters); @@ -926,7 +924,7 @@ FileDialog::FileDialog() { show_hidden_files = default_show_hidden_files; vbox = memnew(VBoxContainer); - add_child(vbox); + add_child(vbox, false, INTERNAL_MODE_FRONT); vbox->connect("theme_changed", callable_mp(this, &FileDialog::_theme_changed)); mode = FILE_MODE_SAVE_FILE; @@ -1025,8 +1023,7 @@ FileDialog::FileDialog() { filter->connect("item_selected", callable_mp(this, &FileDialog::_filter_selected)); confirm_save = memnew(ConfirmationDialog); - // confirm_save->set_as_top_level(true); - add_child(confirm_save); + add_child(confirm_save, false, INTERNAL_MODE_FRONT); confirm_save->connect("confirmed", callable_mp(this, &FileDialog::_save_confirm_pressed)); @@ -1038,16 +1035,16 @@ FileDialog::FileDialog() { makedirname = memnew(LineEdit); makedirname->set_structured_text_bidi_override(Control::STRUCTURED_TEXT_FILE); makevb->add_margin_child(TTRC("Name:"), makedirname); - add_child(makedialog); + 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.")); - add_child(mkdirerr); + add_child(mkdirerr, false, INTERNAL_MODE_FRONT); exterr = memnew(AcceptDialog); exterr->set_text(TTRC("Must use a valid extension.")); - add_child(exterr); + add_child(exterr, false, INTERNAL_MODE_FRONT); update_filters(); update_dir(); diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h index 7fbafc4bb4..b5190bdab1 100644 --- a/scene/gui/file_dialog.h +++ b/scene/gui/file_dialog.h @@ -132,7 +132,7 @@ private: void _update_drives(); - void _unhandled_input(const Ref<InputEvent> &p_event); + virtual void unhandled_input(const Ref<InputEvent> &p_event) override; bool _is_open_should_be_disabled(); diff --git a/scene/gui/gradient_edit.cpp b/scene/gui/gradient_edit.cpp index 7278ca6e94..56b8a936e1 100644 --- a/scene/gui/gradient_edit.cpp +++ b/scene/gui/gradient_edit.cpp @@ -48,10 +48,7 @@ GradientEdit::GradientEdit() { picker = memnew(ColorPicker); popup->add_child(picker); - add_child(popup); - - checker = Ref<ImageTexture>(memnew(ImageTexture)); - Ref<Image> img = memnew(Image(checker_bg_png)); + add_child(popup, false, INTERNAL_MODE_FRONT); } int GradientEdit::_get_point_from_pos(int x) { @@ -91,7 +88,7 @@ void GradientEdit::_show_color_picker() { GradientEdit::~GradientEdit() { } -void GradientEdit::_gui_input(const Ref<InputEvent> &p_event) { +void GradientEdit::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventKey> k = p_event; @@ -101,7 +98,7 @@ void GradientEdit::_gui_input(const Ref<InputEvent> &p_event) { grabbed = -1; grabbing = false; update(); - emit_signal("ramp_changed"); + emit_signal(SNAME("ramp_changed")); accept_event(); } @@ -121,7 +118,7 @@ void GradientEdit::_gui_input(const Ref<InputEvent> &p_event) { grabbed = -1; grabbing = false; update(); - emit_signal("ramp_changed"); + emit_signal(SNAME("ramp_changed")); accept_event(); } } @@ -145,7 +142,7 @@ void GradientEdit::_gui_input(const Ref<InputEvent> &p_event) { } } - emit_signal("ramp_changed"); + emit_signal(SNAME("ramp_changed")); update(); } } @@ -214,13 +211,13 @@ void GradientEdit::_gui_input(const Ref<InputEvent> &p_event) { } } - emit_signal("ramp_changed"); + emit_signal(SNAME("ramp_changed")); } if (mb.is_valid() && mb->get_button_index() == 1 && !mb->is_pressed()) { if (grabbing) { grabbing = false; - emit_signal("ramp_changed"); + emit_signal(SNAME("ramp_changed")); } update(); } @@ -288,7 +285,7 @@ void GradientEdit::_gui_input(const Ref<InputEvent> &p_event) { } } - emit_signal("ramp_changed"); + emit_signal(SNAME("ramp_changed")); update(); } @@ -311,7 +308,7 @@ void GradientEdit::_notification(int p_what) { int total_w = get_size().width - get_size().height - SPACING; //Draw checker pattern for ramp - _draw_checker(0, 0, total_w, h); + draw_texture_rect(get_theme_icon(SNAME("GuiMiniCheckerboard"), SNAME("EditorIcons")), Rect2(0, 0, total_w, h), true); //Draw color ramp Gradient::Point prev; @@ -378,7 +375,7 @@ void GradientEdit::_notification(int p_what) { } //Draw "button" for color selector - _draw_checker(total_w + SPACING, 0, h, h); + draw_texture_rect(get_theme_icon(SNAME("GuiMiniCheckerboard"), SNAME("EditorIcons")), Rect2(total_w + SPACING, 0, h, h), true); if (grabbed != -1) { //Draw with selection color draw_rect(Rect2(total_w + SPACING, 0, h, h), points[grabbed].color); @@ -405,27 +402,6 @@ void GradientEdit::_notification(int p_what) { } } -void GradientEdit::_draw_checker(int x, int y, int w, int h) { - //Draw it with polygon to insert UVs for scale - Vector<Vector2> backPoints; - backPoints.push_back(Vector2(x, y)); - backPoints.push_back(Vector2(x, y + h)); - backPoints.push_back(Vector2(x + w, y + h)); - backPoints.push_back(Vector2(x + w, y)); - Vector<Color> colorPoints; - colorPoints.push_back(Color(1, 1, 1, 1)); - colorPoints.push_back(Color(1, 1, 1, 1)); - colorPoints.push_back(Color(1, 1, 1, 1)); - colorPoints.push_back(Color(1, 1, 1, 1)); - Vector<Vector2> uvPoints; - //Draw checker pattern pixel-perfect and scale it by 2. - uvPoints.push_back(Vector2(x, y)); - uvPoints.push_back(Vector2(x, y + h * .5f / checker->get_height())); - uvPoints.push_back(Vector2(x + w * .5f / checker->get_width(), y + h * .5f / checker->get_height())); - uvPoints.push_back(Vector2(x + w * .5f / checker->get_width(), y)); - draw_polygon(backPoints, colorPoints, uvPoints, checker); -} - Size2 GradientEdit::get_minimum_size() const { return Vector2(0, 16); } @@ -436,10 +412,10 @@ void GradientEdit::_color_changed(const Color &p_color) { } points.write[grabbed].color = p_color; update(); - emit_signal("ramp_changed"); + emit_signal(SNAME("ramp_changed")); } -void GradientEdit::set_ramp(const Vector<float> &p_offsets, const Vector<Color> &p_colors) { +void GradientEdit::set_ramp(const Vector<real_t> &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++) { @@ -453,8 +429,8 @@ void GradientEdit::set_ramp(const Vector<float> &p_offsets, const Vector<Color> update(); } -Vector<float> GradientEdit::get_offsets() const { - Vector<float> ret; +Vector<real_t> GradientEdit::get_offsets() const { + Vector<real_t> ret; for (int i = 0; i < points.size(); i++) { ret.push_back(points[i].offset); } @@ -482,6 +458,5 @@ Vector<Gradient::Point> &GradientEdit::get_points() { } void GradientEdit::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &GradientEdit::_gui_input); ADD_SIGNAL(MethodInfo("ramp_changed")); } diff --git a/scene/gui/gradient_edit.h b/scene/gui/gradient_edit.h index eb7367d598..a173631963 100644 --- a/scene/gui/gradient_edit.h +++ b/scene/gui/gradient_edit.h @@ -42,8 +42,6 @@ class GradientEdit : public Control { PopupPanel *popup; ColorPicker *picker; - Ref<ImageTexture> checker; - bool grabbing = false; int grabbed = -1; Vector<Gradient::Point> points; @@ -54,13 +52,13 @@ class GradientEdit : public Control { void _show_color_picker(); protected: - void _gui_input(const Ref<InputEvent> &p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; void _notification(int p_what); static void _bind_methods(); public: - void set_ramp(const Vector<float> &p_offsets, const Vector<Color> &p_colors); - Vector<float> get_offsets() const; + void set_ramp(const Vector<real_t> &p_offsets, const Vector<Color> &p_colors); + Vector<real_t> 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 39aa6749e7..b9b02b1427 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -51,10 +51,6 @@ GraphEditFilter::GraphEditFilter(GraphEdit *p_edit) { ge = p_edit; } -void GraphEditMinimap::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &GraphEditMinimap::_gui_input); -} - GraphEditMinimap::GraphEditMinimap(GraphEdit *p_edit) { ge = p_edit; @@ -148,7 +144,7 @@ Vector2 GraphEditMinimap::_convert_to_graph_position(const Vector2 &p_position) return graph_position; } -void GraphEditMinimap::_gui_input(const Ref<InputEvent> &p_ev) { +void GraphEditMinimap::gui_input(const Ref<InputEvent> &p_ev) { ERR_FAIL_COND(p_ev.is_null()); if (!ge->is_minimap_enabled()) { @@ -162,7 +158,7 @@ void GraphEditMinimap::_gui_input(const Ref<InputEvent> &p_ev) { if (mb->is_pressed()) { is_pressing = true; - Ref<Texture2D> resizer = get_theme_icon("resizer"); + Ref<Texture2D> resizer = get_theme_icon(SNAME("resizer")); Rect2 resizer_hitbox = Rect2(Point2(), resizer->get_size()); if (resizer_hitbox.has_point(mb->get_position())) { is_resizing = true; @@ -217,8 +213,8 @@ Error GraphEdit::connect_node(const StringName &p_from, int p_from_port, const S } bool GraphEdit::is_node_connected(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port) { - for (List<Connection>::Element *E = connections.front(); E; E = E->next()) { - if (E->get().from == p_from && E->get().from_port == p_from_port && E->get().to == p_to && E->get().to_port == p_to_port) { + for (const Connection &E : connections) { + if (E.from == p_from && E.from_port == p_from_port && E.to == p_to && E.to_port == p_to_port) { return true; } } @@ -227,7 +223,7 @@ bool GraphEdit::is_node_connected(const StringName &p_from, int p_from_port, con } void GraphEdit::disconnect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port) { - for (List<Connection>::Element *E = connections.front(); E; E = E->next()) { + for (const List<Connection>::Element *E = connections.front(); E; E = E->next()) { if (E->get().from == p_from && E->get().from_port == p_from_port && E->get().to == p_to && E->get().to_port == p_to_port) { connections.erase(E); top_layer->update(); @@ -257,7 +253,7 @@ Vector2 GraphEdit::get_scroll_ofs() const { void GraphEdit::_scroll_moved(double) { if (!awaiting_scroll_offset_update) { - call_deferred("_update_scroll_offset"); + call_deferred(SNAME("_update_scroll_offset")); awaiting_scroll_offset_update = true; } top_layer->update(); @@ -265,7 +261,7 @@ void GraphEdit::_scroll_moved(double) { update(); if (!setting_scroll_ofs) { //in godot, signals on change value are avoided as a convention - emit_signal("scroll_offset_changed", get_scroll_ofs()); + emit_signal(SNAME("scroll_offset_changed"), get_scroll_ofs()); } } @@ -345,7 +341,7 @@ void GraphEdit::_update_scroll() { set_block_minimum_size_adjust(false); if (!awaiting_scroll_offset_update) { - call_deferred("_update_scroll_offset"); + call_deferred(SNAME("_update_scroll_offset")); awaiting_scroll_offset_update = true; } @@ -360,18 +356,7 @@ void GraphEdit::_graph_node_raised(Node *p_gn) { } else { gn->raise(); } - int first_not_comment = 0; - for (int i = 0; i < get_child_count(); i++) { - GraphNode *gn2 = Object::cast_to<GraphNode>(get_child(i)); - if (gn2 && !gn2->is_comment()) { - first_not_comment = i; - break; - } - } - - move_child(connections_layer, first_not_comment); - top_layer->raise(); - emit_signal("node_selected", p_gn); + emit_signal(SNAME("node_selected"), p_gn); } void GraphEdit::_graph_node_moved(Node *p_gn) { @@ -395,7 +380,7 @@ void GraphEdit::_graph_node_slot_updated(int p_index, Node *p_gn) { void GraphEdit::add_child_notify(Node *p_child) { Control::add_child_notify(p_child); - top_layer->call_deferred("raise"); // Top layer always on top! + top_layer->call_deferred(SNAME("raise")); // Top layer always on top! GraphNode *gn = Object::cast_to<GraphNode>(p_child); if (gn) { @@ -421,7 +406,7 @@ void GraphEdit::remove_child_notify(Node *p_child) { } if (top_layer != nullptr && is_inside_tree()) { - top_layer->call_deferred("raise"); // Top layer always on top! + top_layer->call_deferred(SNAME("raise")); // Top layer always on top! } GraphNode *gn = Object::cast_to<GraphNode>(p_child); @@ -442,14 +427,15 @@ void GraphEdit::remove_child_notify(Node *p_child) { void GraphEdit::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { - port_grab_distance_horizontal = get_theme_constant("port_grab_distance_horizontal"); - port_grab_distance_vertical = get_theme_constant("port_grab_distance_vertical"); - - zoom_minus->set_icon(get_theme_icon("minus")); - zoom_reset->set_icon(get_theme_icon("reset")); - zoom_plus->set_icon(get_theme_icon("more")); - snap_button->set_icon(get_theme_icon("snap")); - minimap_button->set_icon(get_theme_icon("minimap")); + port_grab_distance_horizontal = get_theme_constant(SNAME("port_grab_distance_horizontal")); + port_grab_distance_vertical = get_theme_constant(SNAME("port_grab_distance_vertical")); + + zoom_minus->set_icon(get_theme_icon(SNAME("minus"))); + zoom_reset->set_icon(get_theme_icon(SNAME("reset"))); + zoom_plus->set_icon(get_theme_icon(SNAME("more"))); + snap_button->set_icon(get_theme_icon(SNAME("snap"))); + minimap_button->set_icon(get_theme_icon(SNAME("minimap"))); + layout_button->set_icon(get_theme_icon(SNAME("layout"))); } if (p_what == NOTIFICATION_READY) { Size2 hmin = h_scroll->get_combined_minimum_size(); @@ -466,7 +452,7 @@ void GraphEdit::_notification(int p_what) { v_scroll->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0); } if (p_what == NOTIFICATION_DRAW) { - draw_style_box(get_theme_stylebox("bg"), Rect2(Point2(), get_size())); + draw_style_box(get_theme_stylebox(SNAME("bg")), Rect2(Point2(), get_size())); if (is_using_snap()) { //draw grid @@ -479,8 +465,8 @@ void GraphEdit::_notification(int p_what) { Point2i from = (offset / float(snap)).floor(); Point2i len = (size / float(snap)).floor() + Vector2(1, 1); - Color grid_minor = get_theme_color("grid_minor"); - Color grid_major = get_theme_color("grid_major"); + Color grid_minor = get_theme_color(SNAME("grid_minor")); + Color grid_major = get_theme_color(SNAME("grid_major")); for (int i = from.x; i < from.x + len.x; i++) { Color color; @@ -518,7 +504,8 @@ void GraphEdit::_notification(int p_what) { } bool GraphEdit::_filter_input(const Point2 &p_point) { - Ref<Texture2D> port = get_theme_icon("port", "GraphNode"); + Ref<Texture2D> port = get_theme_icon(SNAME("port"), SNAME("GraphNode")); + Vector2i port_size = Vector2i(port->get_width(), port->get_height()); for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); @@ -528,14 +515,14 @@ bool GraphEdit::_filter_input(const Point2 &p_point) { for (int j = 0; j < gn->get_connection_output_count(); j++) { Vector2 pos = gn->get_connection_output_position(j) + gn->get_position(); - if (is_in_hot_zone(pos / zoom, p_point / zoom)) { + if (is_in_hot_zone(pos / zoom, p_point / zoom, port_size, false)) { return true; } } for (int j = 0; j < gn->get_connection_input_count(); j++) { Vector2 pos = gn->get_connection_input_position(j) + gn->get_position(); - if (is_in_hot_zone(pos / zoom, p_point / zoom)) { + if (is_in_hot_zone(pos / zoom, p_point / zoom, port_size, true)) { return true; } } @@ -547,8 +534,10 @@ bool GraphEdit::_filter_input(const Point2 &p_point) { void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { Ref<InputEventMouseButton> mb = p_ev; if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT && mb->is_pressed()) { + Ref<Texture2D> port = get_theme_icon(SNAME("port"), SNAME("GraphNode")); + Vector2i port_size = Vector2i(port->get_width(), port->get_height()); + connecting_valid = false; - Ref<Texture2D> port = get_theme_icon("port", "GraphNode"); click_pos = mb->get_position() / zoom; for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); @@ -558,23 +547,23 @@ 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(); - if (is_in_hot_zone(pos / zoom, click_pos)) { + if (is_in_hot_zone(pos / zoom, click_pos, port_size, false)) { if (valid_left_disconnect_types.has(gn->get_connection_output_type(j))) { //check disconnect - for (List<Connection>::Element *E = connections.front(); E; E = E->next()) { - if (E->get().from == gn->get_name() && E->get().from_port == j) { - Node *to = get_node(String(E->get().to)); + for (const Connection &E : connections) { + if (E.from == gn->get_name() && E.from_port == j) { + Node *to = get_node(String(E.to)); if (Object::cast_to<GraphNode>(to)) { - connecting_from = E->get().to; - connecting_index = E->get().to_port; + connecting_from = E.to; + connecting_index = E.to_port; connecting_out = false; - connecting_type = Object::cast_to<GraphNode>(to)->get_connection_input_type(E->get().to_port); - connecting_color = Object::cast_to<GraphNode>(to)->get_connection_input_color(E->get().to_port); + connecting_type = Object::cast_to<GraphNode>(to)->get_connection_input_type(E.to_port); + connecting_color = Object::cast_to<GraphNode>(to)->get_connection_input_color(E.to_port); connecting_target = false; connecting_to = pos; just_disconnected = true; - emit_signal("disconnection_request", E->get().from, E->get().from_port, E->get().to, E->get().to_port); + emit_signal(SNAME("disconnection_request"), E.from, E.from_port, E.to, E.to_port); to = get_node(String(connecting_from)); //maybe it was erased if (Object::cast_to<GraphNode>(to)) { connecting = true; @@ -600,23 +589,23 @@ 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(); - if (is_in_hot_zone(pos / zoom, click_pos)) { + if (is_in_hot_zone(pos / zoom, click_pos, port_size, true)) { if (right_disconnects || valid_right_disconnect_types.has(gn->get_connection_input_type(j))) { //check disconnect - for (List<Connection>::Element *E = connections.front(); E; E = E->next()) { - if (E->get().to == gn->get_name() && E->get().to_port == j) { - Node *fr = get_node(String(E->get().from)); + for (const Connection &E : connections) { + if (E.to == gn->get_name() && E.to_port == j) { + Node *fr = get_node(String(E.from)); if (Object::cast_to<GraphNode>(fr)) { - connecting_from = E->get().from; - connecting_index = E->get().from_port; + connecting_from = E.from; + connecting_index = E.from_port; connecting_out = true; - connecting_type = Object::cast_to<GraphNode>(fr)->get_connection_output_type(E->get().from_port); - connecting_color = Object::cast_to<GraphNode>(fr)->get_connection_output_color(E->get().from_port); + connecting_type = Object::cast_to<GraphNode>(fr)->get_connection_output_type(E.from_port); + connecting_color = Object::cast_to<GraphNode>(fr)->get_connection_output_color(E.from_port); connecting_target = false; connecting_to = pos; just_disconnected = true; - emit_signal("disconnection_request", E->get().from, E->get().from_port, E->get().to, E->get().to_port); + emit_signal(SNAME("disconnection_request"), E.from, E.from_port, E.to, E.to_port); fr = get_node(String(connecting_from)); //maybe it was erased if (Object::cast_to<GraphNode>(fr)) { connecting = true; @@ -649,10 +638,12 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { connecting_target = false; top_layer->update(); minimap->update(); - connecting_valid = just_disconnected || click_pos.distance_to(connecting_to / zoom) > 20.0 * zoom; + connecting_valid = just_disconnected || click_pos.distance_to(connecting_to / zoom) > 20.0; if (connecting_valid) { - Ref<Texture2D> port = get_theme_icon("port", "GraphNode"); + 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--) { GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); @@ -664,7 +655,7 @@ 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(); int type = gn->get_connection_output_type(j); - if ((type == connecting_type || valid_connection_types.has(ConnType(type, connecting_type))) && is_in_hot_zone(pos / zoom, mpos)) { + if ((type == connecting_type || valid_connection_types.has(ConnType(type, connecting_type))) && is_in_hot_zone(pos / zoom, mpos, port_size, false)) { connecting_target = true; connecting_to = pos; connecting_target_to = gn->get_name(); @@ -676,7 +667,7 @@ 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(); int type = gn->get_connection_input_type(j); - if ((type == connecting_type || valid_connection_types.has(ConnType(type, connecting_type))) && is_in_hot_zone(pos / zoom, mpos)) { + if ((type == connecting_type || valid_connection_types.has(ConnType(type, connecting_type))) && is_in_hot_zone(pos / zoom, mpos, port_size, true)) { connecting_target = true; connecting_to = pos; connecting_target_to = gn->get_name(); @@ -701,7 +692,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { SWAP(from, to); SWAP(from_slot, to_slot); } - emit_signal("connection_request", from, from_slot, to, to_slot); + emit_signal(SNAME("connection_request"), from, from_slot, to, to_slot); } else if (!just_disconnected) { String from = connecting_from; @@ -709,9 +700,9 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { Vector2 ofs = Vector2(mb->get_position().x, mb->get_position().y); if (!connecting_out) { - emit_signal("connection_from_empty", from, from_slot, ofs); + emit_signal(SNAME("connection_from_empty"), from, from_slot, ofs); } else { - emit_signal("connection_to_empty", from, from_slot, ofs); + emit_signal(SNAME("connection_to_empty"), from, from_slot, ofs); } } } @@ -747,9 +738,25 @@ bool GraphEdit::_check_clickable_control(Control *p_control, const Vector2 &pos) } } -bool GraphEdit::is_in_hot_zone(const Vector2 &pos, const Vector2 &p_mouse_pos) { - 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)) { - return false; +bool GraphEdit::is_in_hot_zone(const Vector2 &pos, const Vector2 &p_mouse_pos, const Vector2i &p_port_size, bool p_left) { + if (p_left) { + if (!Rect2( + pos.x - p_port_size.x / 2 - port_grab_distance_horizontal, + pos.y - p_port_size.y / 2 - port_grab_distance_vertical / 2, + p_port_size.x + port_grab_distance_horizontal, + p_port_size.y + port_grab_distance_vertical) + .has_point(p_mouse_pos)) { + return false; + } + } else { + if (!Rect2( + pos.x - p_port_size.x / 2, + pos.y - p_port_size.y / 2 - port_grab_distance_vertical / 2, + p_port_size.x + port_grab_distance_horizontal, + p_port_size.y + port_grab_distance_vertical) + .has_point(p_mouse_pos)) { + return false; + } } for (int i = 0; i < get_child_count(); i++) { @@ -783,73 +790,41 @@ bool GraphEdit::is_in_hot_zone(const Vector2 &pos, const Vector2 &p_mouse_pos) { return true; } -template <class Vector2> -static _FORCE_INLINE_ Vector2 _bezier_interp(real_t t, Vector2 start, Vector2 control_1, Vector2 control_2, 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; -} - -void GraphEdit::_bake_segment2d(Vector<Vector2> &points, Vector<Color> &colors, float p_begin, float p_end, const Vector2 &p_a, const Vector2 &p_out, const Vector2 &p_b, const Vector2 &p_in, int p_depth, int p_min_depth, int p_max_depth, float p_tol, const Color &p_color, const Color &p_to_color, int &lines) const { - float 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 na = (mid - beg).normalized(); - Vector2 nb = (end - mid).normalized(); - float dp = Math::rad2deg(Math::acos(na.dot(nb))); - - if (p_depth >= p_min_depth && (dp < p_tol || p_depth >= p_max_depth)) { - points.push_back((beg + end) * 0.5); - colors.push_back(p_color.lerp(p_to_color, mp)); - lines++; - } else { - _bake_segment2d(points, colors, p_begin, mp, p_a, p_out, p_b, p_in, p_depth + 1, p_min_depth, p_max_depth, p_tol, p_color, p_to_color, lines); - _bake_segment2d(points, colors, mp, p_end, p_a, p_out, p_b, p_in, p_depth + 1, p_min_depth, p_max_depth, p_tol, p_color, p_to_color, lines); +PackedVector2Array GraphEdit::get_connection_line(const Vector2 &p_from, const Vector2 &p_to) { + Vector<Vector2> ret; + if (GDVIRTUAL_CALL(_get_connection_line, p_from, p_to, ret)) { + return ret; } -} - -void GraphEdit::_draw_cos_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_bezier_ratio) { - //cubic bezier code - float diff = p_to.x - p_from.x; - float cp_offset; - int cp_len = get_theme_constant("bezier_len_pos") * p_bezier_ratio; - int cp_neg_len = get_theme_constant("bezier_len_neg") * p_bezier_ratio; - if (diff > 0) { - cp_offset = MIN(cp_len, diff * 0.5); - } else { - cp_offset = MAX(MIN(cp_len - diff, cp_neg_len), -diff * 0.5); - } - - Vector2 c1 = Vector2(cp_offset * zoom, 0); - Vector2 c2 = Vector2(-cp_offset * zoom, 0); - - int lines = 0; + Curve2D curve; + Vector<Color> colors; + curve.add_point(p_from); + curve.set_point_out(0, Vector2(60, 0)); + curve.add_point(p_to); + curve.set_point_in(1, Vector2(-60, 0)); + return curve.tessellate(); +} - Vector<Point2> points; +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) { + Vector<Vector2> points = get_connection_line(p_from / p_zoom, p_to / p_zoom); + Vector<Vector2> scaled_points; Vector<Color> colors; - points.push_back(p_from); - colors.push_back(p_color); - _bake_segment2d(points, colors, 0, 1, p_from, c1, p_to, c2, 0, 3, 9, 3, p_color, p_to_color, lines); - points.push_back(p_to); - colors.push_back(p_to_color); + float length = (p_from / p_zoom).distance_to(p_to / p_zoom); + for (int i = 0; i < points.size(); i++) { + float d = (p_from / p_zoom).distance_to(points[i]) / length; + colors.push_back(p_color.lerp(p_to_color, d)); + scaled_points.push_back(points[i] * p_zoom); + } #ifdef TOOLS_ENABLED - p_where->draw_polyline_colors(points, colors, Math::floor(p_width * EDSCALE), lines_antialiased); + p_where->draw_polyline_colors(scaled_points, colors, Math::floor(p_width * EDSCALE), lines_antialiased); #else - p_where->draw_polyline_colors(points, colors, p_width, lines_antialiased); + p_where->draw_polyline_colors(scaled_points, colors, p_width, lines_antialiased); #endif } void GraphEdit::_connections_layer_draw() { - Color activity_color = get_theme_color("activity"); + Color activity_color = get_theme_color(SNAME("activity")); //draw connections List<List<Connection>::Element *> to_erase; for (List<Connection>::Element *E = connections.front(); E; E = E->next()) { @@ -891,7 +866,7 @@ void GraphEdit::_connections_layer_draw() { color = color.lerp(activity_color, E->get().activity); tocolor = tocolor.lerp(activity_color, E->get().activity); } - _draw_cos_line(connections_layer, frompos, topos, color, tocolor, lines_thickness); + _draw_connection_line(connections_layer, frompos, topos, color, tocolor, lines_thickness, zoom); } while (to_erase.size()) { @@ -930,12 +905,12 @@ void GraphEdit::_top_layer_draw() { if (!connecting_out) { SWAP(pos, topos); } - _draw_cos_line(top_layer, pos, topos, col, col, lines_thickness); + _draw_connection_line(top_layer, pos, topos, col, col, lines_thickness, zoom); } if (box_selecting) { - top_layer->draw_rect(box_selecting_rect, get_theme_color("selection_fill")); - top_layer->draw_rect(box_selecting_rect, get_theme_color("selection_stroke"), false); + top_layer->draw_rect(box_selecting_rect, get_theme_color(SNAME("selection_fill"))); + top_layer->draw_rect(box_selecting_rect, get_theme_color(SNAME("selection_stroke")), false); } } @@ -948,7 +923,7 @@ void GraphEdit::_minimap_draw() { // Draw the minimap background. Rect2 minimap_rect = Rect2(Point2(), minimap->get_size()); - minimap->draw_style_box(minimap->get_theme_stylebox("bg"), minimap_rect); + minimap->draw_style_box(minimap->get_theme_stylebox(SNAME("bg")), minimap_rect); Vector2 graph_offset = minimap->_get_graph_offset(); Vector2 minimap_offset = minimap->minimap_offset; @@ -964,7 +939,7 @@ void GraphEdit::_minimap_draw() { Vector2 node_size = minimap->_convert_from_graph_position(gn->get_size() * zoom); Rect2 node_rect = Rect2(node_position, node_size); - Ref<StyleBoxFlat> sb_minimap = minimap->get_theme_stylebox("node")->duplicate(); + 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"); @@ -987,7 +962,7 @@ void GraphEdit::_minimap_draw() { Vector2 node_size = minimap->_convert_from_graph_position(gn->get_size() * zoom); Rect2 node_rect = Rect2(node_position, node_size); - Ref<StyleBoxFlat> sb_minimap = minimap->get_theme_stylebox("node")->duplicate(); + 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"); @@ -1000,9 +975,9 @@ void GraphEdit::_minimap_draw() { } // Draw node connections. - Color activity_color = get_theme_color("activity"); - for (List<Connection>::Element *E = connections.front(); E; E = E->next()) { - NodePath fromnp(E->get().from); + Color activity_color = get_theme_color(SNAME("activity")); + for (const Connection &E : connections) { + NodePath fromnp(E.from); Node *from = get_node(fromnp); if (!from) { @@ -1013,7 +988,7 @@ void GraphEdit::_minimap_draw() { continue; } - NodePath tonp(E->get().to); + NodePath tonp(E.to); Node *to = get_node(tonp); if (!to) { continue; @@ -1023,27 +998,27 @@ void GraphEdit::_minimap_draw() { continue; } - Vector2 from_slot_position = gfrom->get_position_offset() * zoom + gfrom->get_connection_output_position(E->get().from_port); + Vector2 from_slot_position = gfrom->get_position_offset() * zoom + gfrom->get_connection_output_position(E.from_port); Vector2 from_position = minimap->_convert_from_graph_position(from_slot_position - graph_offset) + minimap_offset; - Color from_color = gfrom->get_connection_output_color(E->get().from_port); - Vector2 to_slot_position = gto->get_position_offset() * zoom + gto->get_connection_input_position(E->get().to_port); + Color from_color = gfrom->get_connection_output_color(E.from_port); + Vector2 to_slot_position = gto->get_position_offset() * zoom + gto->get_connection_input_position(E.to_port); Vector2 to_position = minimap->_convert_from_graph_position(to_slot_position - graph_offset) + minimap_offset; - Color to_color = gto->get_connection_input_color(E->get().to_port); + Color to_color = gto->get_connection_input_color(E.to_port); - if (E->get().activity > 0) { - from_color = from_color.lerp(activity_color, E->get().activity); - to_color = to_color.lerp(activity_color, E->get().activity); + if (E.activity > 0) { + from_color = from_color.lerp(activity_color, E.activity); + to_color = to_color.lerp(activity_color, E.activity); } - _draw_cos_line(minimap, from_position, to_position, from_color, to_color, 1.0, 0.5); + _draw_connection_line(minimap, from_position, to_position, from_color, to_color, 0.1, minimap->_convert_from_graph_position(Vector2(zoom, zoom)).length()); } // Draw the "camera" viewport. Rect2 camera_rect = minimap->get_camera_rect(); - minimap->draw_style_box(minimap->get_theme_stylebox("camera"), camera_rect); + minimap->draw_style_box(minimap->get_theme_stylebox(SNAME("camera")), camera_rect); // Draw the resizer control. - Ref<Texture2D> resizer = minimap->get_theme_icon("resizer"); - Color resizer_color = minimap->get_theme_color("resizer_color"); + Ref<Texture2D> resizer = minimap->get_theme_icon(SNAME("resizer")); + Color resizer_color = minimap->get_theme_color(SNAME("resizer_color")); minimap->draw_texture(resizer, Point2(), resizer_color); } @@ -1058,7 +1033,7 @@ void GraphEdit::set_selected(Node *p_child) { } } -void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { +void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { ERR_FAIL_COND(p_ev.is_null()); Ref<InputEventMouseMotion> mm = p_ev; @@ -1070,7 +1045,7 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { if (mm.is_valid() && dragging) { if (!moving_selection) { - emit_signal("begin_node_move"); + emit_signal(SNAME("begin_node_move")); moving_selection = true; } @@ -1113,17 +1088,17 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { if (in_box) { if (!gn->is_selected() && box_selection_mode_additive) { - emit_signal("node_selected", gn); + emit_signal(SNAME("node_selected"), gn); } else if (gn->is_selected() && !box_selection_mode_additive) { - emit_signal("node_deselected", gn); + emit_signal(SNAME("node_deselected"), gn); } gn->set_selected(box_selection_mode_additive); } else { bool select = (previous_selected.find(gn) != nullptr); if (gn->is_selected() && !select) { - emit_signal("node_deselected", gn); + emit_signal(SNAME("node_deselected"), gn); } else if (!gn->is_selected() && select) { - emit_signal("node_selected", gn); + emit_signal(SNAME("node_selected"), gn); } gn->set_selected(select); } @@ -1146,9 +1121,9 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { bool select = (previous_selected.find(gn) != nullptr); if (gn->is_selected() && !select) { - emit_signal("node_deselected", gn); + emit_signal(SNAME("node_deselected"), gn); } else if (!gn->is_selected() && select) { - emit_signal("node_selected", gn); + emit_signal(SNAME("node_selected"), gn); } gn->set_selected(select); } @@ -1160,7 +1135,7 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { top_layer->update(); minimap->update(); } else { - emit_signal("popup_request", b->get_global_position()); + emit_signal(SNAME("popup_request"), b->get_global_position()); } } } @@ -1175,7 +1150,7 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { Rect2 r = gn->get_rect(); r.size *= zoom; if (r.has_point(b->get_position())) { - emit_signal("node_deselected", gn); + emit_signal(SNAME("node_deselected"), gn); gn->set_selected(false); } } @@ -1192,7 +1167,7 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { } if (moving_selection) { - emit_signal("end_node_move"); + emit_signal(SNAME("end_node_move")); moving_selection = false; } @@ -1238,7 +1213,7 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { o_gn->set_selected(true); } else { if (o_gn->is_selected()) { - emit_signal("node_deselected", o_gn); + emit_signal(SNAME("node_deselected"), o_gn); } o_gn->set_selected(false); } @@ -1298,7 +1273,7 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { continue; } if (gn2->is_selected()) { - emit_signal("node_deselected", gn2); + emit_signal(SNAME("node_deselected"), gn2); } gn2->set_selected(false); } @@ -1333,16 +1308,16 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { if (p_ev->is_pressed()) { if (p_ev->is_action("ui_graph_duplicate")) { - emit_signal("duplicate_nodes_request"); + emit_signal(SNAME("duplicate_nodes_request")); accept_event(); } else if (p_ev->is_action("ui_copy")) { - emit_signal("copy_nodes_request"); + emit_signal(SNAME("copy_nodes_request")); accept_event(); } else if (p_ev->is_action("ui_paste")) { - emit_signal("paste_nodes_request"); + emit_signal(SNAME("paste_nodes_request")); accept_event(); } else if (p_ev->is_action("ui_graph_delete")) { - emit_signal("delete_nodes_request"); + emit_signal(SNAME("delete_nodes_request")); accept_event(); } } @@ -1360,15 +1335,15 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { } void GraphEdit::set_connection_activity(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port, float p_activity) { - for (List<Connection>::Element *E = connections.front(); E; E = E->next()) { - if (E->get().from == p_from && E->get().from_port == p_from_port && E->get().to == p_to && E->get().to_port == p_to_port) { - if (Math::is_equal_approx(E->get().activity, p_activity)) { + for (Connection &E : connections) { + if (E.from == p_from && E.from_port == p_from_port && E.to == p_to && E.to_port == p_to_port) { + if (Math::is_equal_approx(E.activity, p_activity)) { //update only if changed top_layer->update(); minimap->update(); connections_layer->update(); } - E->get().activity = p_activity; + E.activity = p_activity; return; } } @@ -1500,12 +1475,12 @@ Array GraphEdit::_get_connection_list() const { List<Connection> conns; get_connection_list(&conns); Array arr; - for (List<Connection>::Element *E = conns.front(); E; E = E->next()) { + for (const Connection &E : conns) { Dictionary d; - d["from"] = E->get().from; - d["from_port"] = E->get().from_port; - d["to"] = E->get().to; - d["to_port"] = E->get().to_port; + d["from"] = E.from; + d["from_port"] = E.from_port; + d["to"] = E.to; + d["to_port"] = E.to_port; arr.push_back(d); } return arr; @@ -1646,6 +1621,505 @@ HBoxContainer *GraphEdit::get_zoom_hbox() { return zoom_hb; } +int GraphEdit::_set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u, const Set<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())) + return 0; + } + return r_u.size() == r_v.size(); + } break; + case GraphEdit::IS_SUBSET: { + 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())) + 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()); + } + } + 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()); + } + } + return r_v.size(); + } break; + default: + break; + } + 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>> l; + + Set<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()]; + if (_set_operations(GraphEdit::IS_SUBSET, n, z)) { + Vector<StringName> t; + t.push_back(E->get()); + if (!l.has(current_layer)) { + l.set(current_layer, Vector<StringName>{}); + } + selected = true; + t.append_array(l[current_layer]); + l.set(current_layer, t); + Set<StringName> V; + V.insert(E->get()); + _set_operations(GraphEdit::UNION, u, V); + } + } + if (!selected) { + current_layer++; + _set_operations(GraphEdit::UNION, z, u); + } + selected = false; + } + + return l; +} + +Vector<StringName> GraphEdit::_split(const Vector<StringName> &r_layer, const HashMap<StringName, Dictionary> &r_crossings) { + if (!r_layer.size()) { + return Vector<StringName>(); + } + + StringName p = r_layer[Math::random(0, r_layer.size() - 1)]; + Vector<StringName> left; + Vector<StringName> right; + + for (int i = 0; i < r_layer.size(); i++) { + if (p != r_layer[i]) { + StringName q = r_layer[i]; + int cross_pq = r_crossings[p][q]; + int cross_qp = r_crossings[q][p]; + if (cross_pq > cross_qp) { + left.push_back(q); + } else { + right.push_back(q); + } + } + } + + left.push_back(p); + left.append_array(right); + 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(); + } + + if (r_layers.size() == 1) { + return; + } + + for (unsigned int i = 1; i < r_layers.size(); i++) { + Vector<StringName> lower_layer = r_layers[i]; + Vector<StringName> upper_layer = r_layers[i - 1]; + int r = -1; + + for (int j = 0; j < lower_layer.size(); j++) { + Vector<Pair<int, StringName>> up; + StringName current_node = lower_layer[j]; + for (int k = 0; k < upper_layer.size(); k++) { + StringName adjacent_neighbour = upper_layer[k]; + if (r_upper_neighbours[current_node].has(adjacent_neighbour)) { + up.push_back(Pair<int, StringName>(k, adjacent_neighbour)); + } + } + + int start = up.size() / 2; + int end = up.size() % 2 ? start : start + 1; + for (int p = start; p <= end; p++) { + StringName Align = r_align[current_node]; + if (Align == current_node && r < up[p].first) { + r_align[up[p].second] = lower_layer[j]; + r_root[current_node] = r_root[up[p].second]; + r_align[current_node] = r_root[up[p].second]; + r = up[p].first; + } + } + } + } +} + +void GraphEdit::_crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, Set<StringName>> &r_upper_neighbours) { + if (r_layers.size() == 1) { + return; + } + + for (unsigned int i = 1; i < r_layers.size(); i++) { + Vector<StringName> upper_layer = r_layers[i - 1]; + Vector<StringName> lower_layer = r_layers[i]; + HashMap<StringName, Dictionary> c; + + for (int j = 0; j < lower_layer.size(); j++) { + StringName p = lower_layer[j]; + Dictionary d; + + for (int k = 0; k < lower_layer.size(); k++) { + unsigned int crossings = 0; + StringName q = lower_layer[k]; + + if (j != k) { + for (int h = 1; h < upper_layer.size(); h++) { + if (r_upper_neighbours[p].has(upper_layer[h])) { + for (int g = 0; g < h; g++) { + if (r_upper_neighbours[q].has(upper_layer[g])) { + crossings++; + } + } + } + } + } + d[q] = crossings; + } + c.set(p, d); + } + + r_layers.set(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()) { + real_t left = 0; + StringName u = E->get(); + StringName v = r_align[u]; + while (u != v && (StringName)r_root[u] != v) { + String _connection = String(u) + " " + String(v); + GraphNode *gfrom = Object::cast_to<GraphNode>(r_node_names[u]); + GraphNode *gto = Object::cast_to<GraphNode>(r_node_names[v]); + + Pair<int, int> ports = r_port_info[_connection]; + int pfrom = ports.first; + int pto = ports.second; + Vector2 frompos = gfrom->get_connection_output_position(pfrom); + Vector2 topos = gto->get_connection_input_position(pto); + + real_t s = (real_t)r_inner_shifts[u] + (frompos.y - topos.y) / zoom; + r_inner_shifts[v] = s; + left = MIN(left, s); + + u = v; + v = (StringName)r_align[v]; + } + + u = E->get(); + do { + r_inner_shifts[u] = (real_t)r_inner_shifts[u] - left; + u = (StringName)r_align[u]; + } while (u != E->get()); + } +} + +float GraphEdit::_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) { +#define MAX_ORDER 2147483647 +#define ORDER(node, layers) \ + for (unsigned int i = 0; i < layers.size(); i++) { \ + int index = layers[i].find(node); \ + if (index > 0) { \ + order = index; \ + break; \ + } \ + order = MAX_ORDER; \ + } + + int order = MAX_ORDER; + float threshold = p_current_threshold; + if (p_v == p_w) { + int min_order = MAX_ORDER; + Connection incoming; + for (List<Connection>::Element *E = connections.front(); E; E = E->next()) { + if (E->get().to == p_w) { + ORDER(E->get().from, r_layers); + if (min_order > order) { + min_order = order; + incoming = E->get(); + } + } + } + + if (incoming.from != StringName()) { + GraphNode *gfrom = Object::cast_to<GraphNode>(r_node_names[incoming.from]); + GraphNode *gto = Object::cast_to<GraphNode>(r_node_names[p_w]); + Vector2 frompos = gfrom->get_connection_output_position(incoming.from_port); + Vector2 topos = gto->get_connection_input_position(incoming.to_port); + + //If connected block node is selected, calculate thershold or add current block to list + if (gfrom->is_selected()) { + Vector2 connected_block_pos = r_node_positions[r_root[incoming.from]]; + if (connected_block_pos.y != FLT_MAX) { + //Connected block is placed. Calculate threshold + threshold = connected_block_pos.y + (real_t)r_inner_shift[incoming.from] - (real_t)r_inner_shift[p_w] + frompos.y - topos.y; + } + } + } + } + if (threshold == FLT_MIN && (StringName)r_align[p_w] == p_v) { + //This time, pick an outgoing edge and repeat as above! + int min_order = MAX_ORDER; + Connection outgoing; + for (List<Connection>::Element *E = connections.front(); E; E = E->next()) { + if (E->get().from == p_w) { + ORDER(E->get().to, r_layers); + if (min_order > order) { + min_order = order; + outgoing = E->get(); + } + } + } + + if (outgoing.to != StringName()) { + GraphNode *gfrom = Object::cast_to<GraphNode>(r_node_names[p_w]); + GraphNode *gto = Object::cast_to<GraphNode>(r_node_names[outgoing.to]); + Vector2 frompos = gfrom->get_connection_output_position(outgoing.from_port); + Vector2 topos = gto->get_connection_input_position(outgoing.to_port); + + //If connected block node is selected, calculate thershold or add current block to list + if (gto->is_selected()) { + Vector2 connected_block_pos = r_node_positions[r_root[outgoing.to]]; + if (connected_block_pos.y != FLT_MAX) { + //Connected block is placed. Calculate threshold + threshold = connected_block_pos.y + (real_t)r_inner_shift[outgoing.to] - (real_t)r_inner_shift[p_w] + frompos.y - topos.y; + } + } + } + } +#undef MAX_ORDER +#undef ORDER + return threshold; +} + +void GraphEdit::_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) { +#define PRED(node, layers) \ + for (unsigned int i = 0; i < layers.size(); i++) { \ + int index = layers[i].find(node); \ + if (index > 0) { \ + predecessor = layers[i][index - 1]; \ + break; \ + } \ + predecessor = StringName(); \ + } + + StringName predecessor; + StringName successor; + Vector2 pos = r_node_positions[p_v]; + + if (pos.y == FLT_MAX) { + pos.y = 0; + bool initial = false; + StringName w = p_v; + real_t threshold = FLT_MIN; + do { + PRED(w, r_layers); + if (predecessor != StringName()) { + StringName u = r_root[predecessor]; + _place_block(u, p_delta, r_layers, r_root, r_align, r_node_name, r_inner_shift, r_sink, r_shift, r_node_positions); + threshold = _calculate_threshold(p_v, w, r_node_name, r_layers, r_root, r_align, r_inner_shift, threshold, r_node_positions); + if ((StringName)r_sink[p_v] == p_v) { + r_sink[p_v] = r_sink[u]; + } + + Vector2 predecessor_root_pos = r_node_positions[u]; + Vector2 predecessor_node_size = Object::cast_to<GraphNode>(r_node_name[predecessor])->get_size(); + if (r_sink[p_v] != r_sink[u]) { + real_t sc = pos.y + (real_t)r_inner_shift[w] - predecessor_root_pos.y - (real_t)r_inner_shift[predecessor] - predecessor_node_size.y - p_delta; + r_shift[r_sink[u]] = MIN(sc, (real_t)r_shift[r_sink[u]]); + } else { + real_t sb = predecessor_root_pos.y + (real_t)r_inner_shift[predecessor] + predecessor_node_size.y - (real_t)r_inner_shift[w] + p_delta; + sb = MAX(sb, threshold); + if (initial) { + pos.y = sb; + } else { + pos.y = MAX(pos.y, sb); + } + initial = false; + } + } + 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); + } + +#undef PRED +} + +void GraphEdit::arrange_nodes() { + if (!arranging_graph) { + arranging_graph = true; + } else { + return; + } + + Dictionary node_names; + Set<StringName> selected_nodes; + + for (int i = get_child_count() - 1; i >= 0; i--) { + GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); + if (!gn) { + continue; + } + + node_names[gn->get_name()] = gn; + } + + HashMap<StringName, Set<StringName>> upper_neighbours; + HashMap<StringName, Pair<int, int>> port_info; + Vector2 origin(FLT_MAX, FLT_MAX); + + float gap_v = 100.0f; + float gap_h = 100.0f; + + for (int i = get_child_count() - 1; i >= 0; i--) { + GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); + if (!gn) { + continue; + } + + if (gn->is_selected()) { + selected_nodes.insert(gn->get_name()); + Set<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()) { + if (!s.has(p_from->get_name())) { + s.insert(p_from->get_name()); + } + String s_connection = String(p_from->get_name()) + " " + String(E->get().to); + StringName _connection(s_connection); + Pair<int, int> ports(E->get().from_port, E->get().to_port); + if (port_info.has(_connection)) { + Pair<int, int> p_ports = port_info[_connection]; + if (p_ports.first < ports.first) { + ports = p_ports; + } + } + port_info.set(_connection, ports); + } + } + upper_neighbours.set(gn->get_name(), s); + } + } + + if (!selected_nodes.size()) { + arranging_graph = false; + return; + } + + HashMap<int, Vector<StringName>> layers = _layering(selected_nodes, upper_neighbours); + _crossing_minimisation(layers, upper_neighbours); + + Dictionary root, align, sink, shift; + _horizontal_alignment(root, align, layers, upper_neighbours, selected_nodes); + + HashMap<StringName, Vector2> new_positions; + Vector2 default_position(FLT_MAX, FLT_MAX); + Dictionary inner_shift; + Set<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()); + } + } + + _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); + } + 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; + do { + Vector2 cal_pos; + cal_pos.y = start_from + (real_t)inner_shift[u]; + new_positions.set(u, cal_pos); + u = align[u]; + } while (u != E->get()); + } + + //Compute horizontal co-ordinates individually for layers to get uniform gap + float start_from = origin.x; + float largest_node_size = 0.0f; + + for (unsigned int i = 0; i < layers.size(); i++) { + Vector<StringName> layer = layers[i]; + for (int j = 0; j < layer.size(); j++) { + float current_node_size = Object::cast_to<GraphNode>(node_names[layer[j]])->get_size().x; + largest_node_size = MAX(largest_node_size, current_node_size); + } + + for (int j = 0; j < layer.size(); j++) { + float current_node_size = Object::cast_to<GraphNode>(node_names[layer[j]])->get_size().x; + Vector2 cal_pos = new_positions[layer[j]]; + + if (current_node_size == largest_node_size) { + cal_pos.x = start_from; + } else { + float current_node_start_pos = start_from; + if (current_node_size < largest_node_size / 2) { + if (!(i || j)) { + start_from -= (largest_node_size - current_node_size); + } + current_node_start_pos = start_from + largest_node_size - current_node_size; + } + cal_pos.x = current_node_start_pos; + } + new_positions.set(layer[j], cal_pos); + } + + start_from += largest_node_size + gap_h; + largest_node_size = 0.0f; + } + + emit_signal("begin_node_move"); + for (const Set<StringName>::Element *E = selected_nodes.front(); E; E = E->next()) { + GraphNode *gn = Object::cast_to<GraphNode>(node_names[E->get()]); + gn->set_drag(true); + Vector2 pos = (new_positions[E->get()]); + + if (is_using_snap()) { + const int snap = get_snap(); + pos = pos.snapped(Vector2(snap, snap)); + } + gn->set_position_offset(pos); + gn->set_drag(false); + } + emit_signal("end_node_move"); + arranging_graph = false; +} + void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("connect_node", "from", "from_port", "to", "to_port"), &GraphEdit::connect_node); ClassDB::bind_method(D_METHOD("is_node_connected", "from", "from_port", "to", "to_port"), &GraphEdit::is_node_connected); @@ -1663,6 +2137,7 @@ void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("add_valid_connection_type", "from_type", "to_type"), &GraphEdit::add_valid_connection_type); ClassDB::bind_method(D_METHOD("remove_valid_connection_type", "from_type", "to_type"), &GraphEdit::remove_valid_connection_type); ClassDB::bind_method(D_METHOD("is_valid_connection_type", "from_type", "to_type"), &GraphEdit::is_valid_connection_type); + ClassDB::bind_method(D_METHOD("get_connection_line", "from", "to"), &GraphEdit::get_connection_line); ClassDB::bind_method(D_METHOD("set_zoom", "zoom"), &GraphEdit::set_zoom); ClassDB::bind_method(D_METHOD("get_zoom"), &GraphEdit::get_zoom); @@ -1702,13 +2177,16 @@ void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_right_disconnects", "enable"), &GraphEdit::set_right_disconnects); ClassDB::bind_method(D_METHOD("is_right_disconnects_enabled"), &GraphEdit::is_right_disconnects_enabled); - ClassDB::bind_method(D_METHOD("_gui_input"), &GraphEdit::_gui_input); ClassDB::bind_method(D_METHOD("_update_scroll_offset"), &GraphEdit::_update_scroll_offset); ClassDB::bind_method(D_METHOD("get_zoom_hbox"), &GraphEdit::get_zoom_hbox); + ClassDB::bind_method(D_METHOD("arrange_nodes"), &GraphEdit::arrange_nodes); + ClassDB::bind_method(D_METHOD("set_selected", "node"), &GraphEdit::set_selected); + 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"); @@ -1757,14 +2235,14 @@ GraphEdit::GraphEdit() { zoom_max = (1 * Math::pow(zoom_step, 4)); top_layer = memnew(GraphEditFilter(this)); - add_child(top_layer); + 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->connect("draw", callable_mp(this, &GraphEdit::_top_layer_draw)); top_layer->connect("gui_input", callable_mp(this, &GraphEdit::_top_layer_input)); connections_layer = memnew(Control); - add_child(connections_layer); + add_child(connections_layer, false, INTERNAL_MODE_FRONT); connections_layer->connect("draw", callable_mp(this, &GraphEdit::_connections_layer_draw)); connections_layer->set_name("CLAYER"); connections_layer->set_disable_visibility_clip(true); // so it can draw freely and be offset @@ -1851,6 +2329,13 @@ GraphEdit::GraphEdit() { minimap_button->set_focus_mode(FOCUS_NONE); zoom_hb->add_child(minimap_button); + layout_button = memnew(Button); + layout_button->set_flat(true); + zoom_hb->add_child(layout_button); + layout_button->set_tooltip(RTR("Arrange nodes.")); + layout_button->connect("pressed", callable_mp(this, &GraphEdit::arrange_nodes)); + layout_button->set_focus_mode(FOCUS_NONE); + Vector2 minimap_size = Vector2(240, 160); float minimap_opacity = 0.65; diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index 5251de1722..44e50aa3c2 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -62,8 +62,6 @@ class GraphEditMinimap : public Control { GraphEdit *ge; protected: - static void _bind_methods(); - public: GraphEditMinimap(GraphEdit *p_edit); @@ -88,7 +86,7 @@ private: Vector2 _convert_from_graph_position(const Vector2 &p_position); Vector2 _convert_to_graph_position(const Vector2 &p_position); - void _gui_input(const Ref<InputEvent> &p_ev); + virtual void gui_input(const Ref<InputEvent> &p_ev) override; void _adjust_graph_scroll(const Vector2 &p_offset); }; @@ -116,6 +114,8 @@ private: Button *minimap_button; + Button *layout_button; + HScrollBar *h_scroll; VScrollBar *v_scroll; @@ -167,9 +167,8 @@ private: float lines_thickness = 2.0f; bool lines_antialiased = true; - void _bake_segment2d(Vector<Vector2> &points, Vector<Color> &colors, float p_begin, float p_end, const Vector2 &p_a, const Vector2 &p_out, const Vector2 &p_b, const Vector2 &p_in, int p_depth, int p_min_depth, int p_max_depth, float p_tol, const Color &p_color, const Color &p_to_color, int &lines) const; - - void _draw_cos_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_bezier_ratio = 1.0); + PackedVector2Array get_connection_line(const Vector2 &p_from, const Vector2 &p_to); + void _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); void _graph_node_raised(Node *p_gn); void _graph_node_moved(Node *p_gn); @@ -177,14 +176,14 @@ private: void _update_scroll(); void _scroll_moved(double); - void _gui_input(const Ref<InputEvent> &p_ev); + virtual void gui_input(const Ref<InputEvent> &p_ev) override; Control *connections_layer; GraphEditFilter *top_layer; GraphEditMinimap *minimap; void _top_layer_input(const Ref<InputEvent> &p_ev); - bool is_in_hot_zone(const Vector2 &pos, const Vector2 &p_mouse_pos); + bool is_in_hot_zone(const Vector2 &pos, const Vector2 &p_mouse_pos, const Vector2i &p_port_size, bool p_left); void _top_layer_draw(); void _connections_layer_draw(); @@ -230,12 +229,32 @@ private: bool _check_clickable_control(Control *p_control, const Vector2 &pos); + bool arranging_graph = false; + + enum SET_OPERATIONS { + IS_EQUAL, + IS_SUBSET, + DIFFERENCE, + 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); + 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); + 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); + protected: static void _bind_methods(); virtual void add_child_notify(Node *p_child) override; virtual void remove_child_notify(Node *p_child) override; void _notification(int p_what); + GDVIRTUAL2RC(Vector<Vector2>, _get_connection_line, Vector2, Vector2) + public: Error connect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port); bool is_node_connected(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port); @@ -304,6 +323,8 @@ public: HBoxContainer *get_zoom_hbox(); + void arrange_nodes(); + GraphEdit(); }; diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index 836bffdf46..08c8c60d7a 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -180,9 +180,9 @@ void GraphNode::_resort() { /** First pass, determine minimum size AND amount of stretchable elements */ Size2i new_size = get_size(); - Ref<StyleBox> sb = get_theme_stylebox("frame"); + Ref<StyleBox> sb = get_theme_stylebox(SNAME("frame")); - int sep = get_theme_constant("separation"); + int sep = get_theme_constant(SNAME("separation")); bool first = true; int children_count = 0; @@ -323,8 +323,8 @@ void GraphNode::_resort() { bool GraphNode::has_point(const Point2 &p_point) const { if (comment) { - Ref<StyleBox> comment = get_theme_stylebox("comment"); - Ref<Texture2D> resizer = get_theme_icon("resizer"); + Ref<StyleBox> comment = get_theme_stylebox(SNAME("comment")); + Ref<Texture2D> resizer = get_theme_icon(SNAME("resizer")); if (Rect2(get_size() - resizer->get_size(), resizer->get_size()).has_point(p_point)) { return true; @@ -355,18 +355,18 @@ void GraphNode::_notification(int p_what) { //sb=sb->duplicate(); //sb->call("set_modulate",modulate); - Ref<Texture2D> port = get_theme_icon("port"); - Ref<Texture2D> close = get_theme_icon("close"); - Ref<Texture2D> resizer = get_theme_icon("resizer"); - int close_offset = get_theme_constant("close_offset"); - int close_h_offset = get_theme_constant("close_h_offset"); - Color close_color = get_theme_color("close_color"); - Color resizer_color = get_theme_color("resizer_color"); - int title_offset = get_theme_constant("title_offset"); - int title_h_offset = get_theme_constant("title_h_offset"); - Color title_color = get_theme_color("title_color"); + Ref<Texture2D> port = get_theme_icon(SNAME("port")); + Ref<Texture2D> close = get_theme_icon(SNAME("close")); + Ref<Texture2D> resizer = get_theme_icon(SNAME("resizer")); + int close_offset = get_theme_constant(SNAME("close_offset")); + int close_h_offset = get_theme_constant(SNAME("close_h_offset")); + Color close_color = get_theme_color(SNAME("close_color")); + Color resizer_color = get_theme_color(SNAME("resizer_color")); + int title_offset = get_theme_constant(SNAME("title_offset")); + int title_h_offset = get_theme_constant(SNAME("title_h_offset")); + Color title_color = get_theme_color(SNAME("title_color")); Point2i icofs = -port->get_size() * 0.5; - int edgeofs = get_theme_constant("port_offset"); + int edgeofs = get_theme_constant(SNAME("port_offset")); icofs.y += sb->get_margin(SIDE_TOP); draw_style_box(sb, Rect2(Point2(), get_size())); @@ -375,10 +375,10 @@ void GraphNode::_notification(int p_what) { case OVERLAY_DISABLED: { } break; case OVERLAY_BREAKPOINT: { - draw_style_box(get_theme_stylebox("breakpoint"), Rect2(Point2(), get_size())); + draw_style_box(get_theme_stylebox(SNAME("breakpoint")), Rect2(Point2(), get_size())); } break; case OVERLAY_POSITION: { - draw_style_box(get_theme_stylebox("position"), Rect2(Point2(), get_size())); + draw_style_box(get_theme_stylebox(SNAME("position")), Rect2(Point2(), get_size())); } break; } @@ -446,8 +446,8 @@ void GraphNode::_notification(int p_what) { } void GraphNode::_shape() { - Ref<Font> font = get_theme_font("title_font"); - int font_size = get_theme_font_size("title_font_size"); + Ref<Font> font = get_theme_font(SNAME("title_font")); + int font_size = get_theme_font_size(SNAME("title_font_size")); title_buf->clear(); if (text_direction == Control::TEXT_DIRECTION_INHERITED) { @@ -481,7 +481,7 @@ void GraphNode::set_slot(int p_idx, bool p_enable_left, int p_type_left, const C update(); connpos_dirty = true; - emit_signal("slot_updated", p_idx); + emit_signal(SNAME("slot_updated"), p_idx); } void GraphNode::clear_slot(int p_idx) { @@ -510,7 +510,7 @@ void GraphNode::set_slot_enabled_left(int p_idx, bool p_enable_left) { update(); connpos_dirty = true; - emit_signal("slot_updated", p_idx); + emit_signal(SNAME("slot_updated"), p_idx); } void GraphNode::set_slot_type_left(int p_idx, int p_type_left) { @@ -520,7 +520,7 @@ void GraphNode::set_slot_type_left(int p_idx, int p_type_left) { update(); connpos_dirty = true; - emit_signal("slot_updated", p_idx); + emit_signal(SNAME("slot_updated"), p_idx); } int GraphNode::get_slot_type_left(int p_idx) const { @@ -537,7 +537,7 @@ void GraphNode::set_slot_color_left(int p_idx, const Color &p_color_left) { update(); connpos_dirty = true; - emit_signal("slot_updated", p_idx); + emit_signal(SNAME("slot_updated"), p_idx); } Color GraphNode::get_slot_color_left(int p_idx) const { @@ -561,7 +561,7 @@ void GraphNode::set_slot_enabled_right(int p_idx, bool p_enable_right) { update(); connpos_dirty = true; - emit_signal("slot_updated", p_idx); + emit_signal(SNAME("slot_updated"), p_idx); } void GraphNode::set_slot_type_right(int p_idx, int p_type_right) { @@ -571,7 +571,7 @@ void GraphNode::set_slot_type_right(int p_idx, int p_type_right) { update(); connpos_dirty = true; - emit_signal("slot_updated", p_idx); + emit_signal(SNAME("slot_updated"), p_idx); } int GraphNode::get_slot_type_right(int p_idx) const { @@ -588,7 +588,7 @@ void GraphNode::set_slot_color_right(int p_idx, const Color &p_color_right) { update(); connpos_dirty = true; - emit_signal("slot_updated", p_idx); + emit_signal(SNAME("slot_updated"), p_idx); } Color GraphNode::get_slot_color_right(int p_idx) const { @@ -599,14 +599,14 @@ Color GraphNode::get_slot_color_right(int p_idx) const { } Size2 GraphNode::get_minimum_size() const { - int sep = get_theme_constant("separation"); - Ref<StyleBox> sb = get_theme_stylebox("frame"); + int sep = get_theme_constant(SNAME("separation")); + Ref<StyleBox> sb = get_theme_stylebox(SNAME("frame")); bool first = true; Size2 minsize; minsize.x = title_buf->get_size().x; if (show_close) { - Ref<Texture2D> close = get_theme_icon("close"); + Ref<Texture2D> close = get_theme_icon(SNAME("close")); minsize.x += sep + close->get_width(); } @@ -699,7 +699,7 @@ String GraphNode::get_language() const { void GraphNode::set_position_offset(const Vector2 &p_offset) { position_offset = p_offset; - emit_signal("position_offset_changed"); + emit_signal(SNAME("position_offset_changed")); update(); } @@ -720,7 +720,7 @@ void GraphNode::set_drag(bool p_drag) { if (p_drag) { drag_from = get_position_offset(); } else { - emit_signal("dragged", drag_from, get_position_offset()); //useful for undo/redo + emit_signal(SNAME("dragged"), drag_from, get_position_offset()); //useful for undo/redo } } @@ -738,10 +738,10 @@ bool GraphNode::is_close_button_visible() const { } void GraphNode::_connpos_update() { - int edgeofs = get_theme_constant("port_offset"); - int sep = get_theme_constant("separation"); + int edgeofs = get_theme_constant(SNAME("port_offset")); + int sep = get_theme_constant(SNAME("separation")); - Ref<StyleBox> sb = get_theme_stylebox("frame"); + Ref<StyleBox> sb = get_theme_stylebox(SNAME("frame")); conn_input_cache.clear(); conn_output_cache.clear(); int vofs = 0; @@ -863,7 +863,7 @@ Color GraphNode::get_connection_output_color(int p_idx) { return conn_output_cache[p_idx].color; } -void GraphNode::_gui_input(const Ref<InputEvent> &p_ev) { +void GraphNode::gui_input(const Ref<InputEvent> &p_ev) { ERR_FAIL_COND(p_ev.is_null()); Ref<InputEventMouseButton> mb = p_ev; @@ -875,12 +875,12 @@ void GraphNode::_gui_input(const Ref<InputEvent> &p_ev) { if (close_rect.size != Size2() && close_rect.has_point(mpos)) { //send focus to parent get_parent_control()->grab_focus(); - emit_signal("close_request"); + emit_signal(SNAME("close_request")); accept_event(); return; } - Ref<Texture2D> resizer = get_theme_icon("resizer"); + Ref<Texture2D> resizer = get_theme_icon(SNAME("resizer")); if (resizable && mpos.x > get_size().x - resizer->get_width() && mpos.y > get_size().y - resizer->get_height()) { resizing = true; @@ -890,7 +890,7 @@ void GraphNode::_gui_input(const Ref<InputEvent> &p_ev) { return; } - emit_signal("raise_request"); + emit_signal(SNAME("raise_request")); } if (!mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { @@ -904,7 +904,7 @@ void GraphNode::_gui_input(const Ref<InputEvent> &p_ev) { Vector2 diff = mpos - resizing_from; - emit_signal("resize_request", resizing_from_size + diff); + emit_signal(SNAME("resize_request"), resizing_from_size + diff); } } @@ -946,8 +946,6 @@ void GraphNode::_bind_methods() { 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("_gui_input"), &GraphNode::_gui_input); - 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("clear_slot", "idx"), &GraphNode::clear_slot); ClassDB::bind_method(D_METHOD("clear_all_slots"), &GraphNode::clear_all_slots); diff --git a/scene/gui/graph_node.h b/scene/gui/graph_node.h index c70f616b47..c7c7006bfc 100644 --- a/scene/gui/graph_node.h +++ b/scene/gui/graph_node.h @@ -99,7 +99,7 @@ private: Overlay overlay = OVERLAY_DISABLED; protected: - void _gui_input(const Ref<InputEvent> &p_ev); + virtual void gui_input(const Ref<InputEvent> &p_ev) override; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/grid_container.cpp b/scene/gui/grid_container.cpp index a54f5eef06..1107e3a4af 100644 --- a/scene/gui/grid_container.cpp +++ b/scene/gui/grid_container.cpp @@ -38,8 +38,8 @@ void GridContainer::_notification(int p_what) { Set<int> col_expanded; // Columns which have the SIZE_EXPAND flag set. Set<int> row_expanded; // Rows which have the SIZE_EXPAND flag set. - int hsep = get_theme_constant("hseparation"); - int vsep = get_theme_constant("vseparation"); + 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); @@ -213,8 +213,8 @@ Size2 GridContainer::get_minimum_size() const { Map<int, int> col_minw; Map<int, int> row_minh; - int hsep = get_theme_constant("hseparation"); - int vsep = get_theme_constant("vseparation"); + int hsep = get_theme_constant(SNAME("hseparation")); + int vsep = get_theme_constant(SNAME("vseparation")); int max_row = 0; int max_col = 0; diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index b0d54bf8c9..d10ad90c1f 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -42,12 +42,14 @@ 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("font"), get_theme_font_size("font_size"), item.opentype_features, (item.language != "") ? 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.opentype_features, (item.language != "") ? item.language : TranslationServer::get_singleton()->get_tool_locale()); 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); } else { item.text_buf->set_flags(TextServer::BREAK_NONE); } + item.text_buf->set_text_overrun_behavior(text_overrun_behavior); + item.text_buf->set_max_lines_visible(max_text_lines); } int ItemList::add_item(const String &p_item, const Ref<Texture2D> &p_texture, bool p_selectable) { @@ -245,6 +247,7 @@ void ItemList::set_item_custom_bg_color(int p_idx, const Color &p_custom_bg_colo ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].custom_bg = p_custom_bg_color; + update(); } Color ItemList::get_item_custom_bg_color(int p_idx) const { @@ -257,6 +260,7 @@ void ItemList::set_item_custom_fg_color(int p_idx, const Color &p_custom_fg_colo ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].custom_fg = p_custom_fg_color; + update(); } Color ItemList::get_item_custom_fg_color(int p_idx) const { @@ -451,6 +455,7 @@ void ItemList::set_max_text_lines(int 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_max_lines_visible(p_lines); } else { items.write[i].text_buf->set_flags(TextServer::BREAK_NONE); } @@ -532,7 +537,7 @@ Size2 ItemList::Item::get_icon_size() const { return size_result; } -void ItemList::_gui_input(const Ref<InputEvent> &p_event) { +void ItemList::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); double prev_scroll = scroll_bar->get_value(); @@ -548,7 +553,7 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { if (defer_select_single >= 0 && mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT && !mb->is_pressed()) { select(defer_select_single, true); - emit_signal("multi_selected", defer_select_single, true); + emit_signal(SNAME("multi_selected"), defer_select_single, true); defer_select_single = -1; return; } @@ -556,7 +561,7 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { if (mb.is_valid() && (mb->get_button_index() == MOUSE_BUTTON_LEFT || (allow_rmb_select && mb->get_button_index() == MOUSE_BUTTON_RIGHT)) && mb->is_pressed()) { search_string = ""; //any mousepress cancels Vector2 pos = mb->get_position(); - Ref<StyleBox> bg = get_theme_stylebox("bg"); + Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg")); pos -= bg->get_offset(); pos.y += scroll_bar->get_value(); @@ -583,7 +588,7 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { if (select_mode == SELECT_MULTI && items[i].selected && mb->is_command_pressed()) { deselect(i); - emit_signal("multi_selected", i, false); + emit_signal(SNAME("multi_selected"), i, false); } else if (select_mode == SELECT_MULTI && mb->is_shift_pressed() && current >= 0 && current < items.size() && current != i) { int from = current; @@ -595,12 +600,12 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { bool selected = !items[j].selected; select(j, false); if (selected) { - emit_signal("multi_selected", j, true); + emit_signal(SNAME("multi_selected"), j, true); } } if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { - emit_signal("item_rmb_selected", i, get_local_mouse_position()); + emit_signal(SNAME("item_rmb_selected"), i, get_local_mouse_position()); } } else { if (!mb->is_double_click() && !mb->is_command_pressed() && select_mode == SELECT_MULTI && items[i].selectable && !items[i].disabled && items[i].selected && mb->get_button_index() == MOUSE_BUTTON_LEFT) { @@ -609,7 +614,7 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { } if (items[i].selected && mb->get_button_index() == MOUSE_BUTTON_RIGHT) { - emit_signal("item_rmb_selected", i, get_local_mouse_position()); + emit_signal(SNAME("item_rmb_selected"), i, get_local_mouse_position()); } else { bool selected = items[i].selected; @@ -617,16 +622,16 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { if (!selected || allow_reselect) { if (select_mode == SELECT_SINGLE) { - emit_signal("item_selected", i); + emit_signal(SNAME("item_selected"), i); } else { - emit_signal("multi_selected", i, true); + emit_signal(SNAME("multi_selected"), i, true); } } if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { - emit_signal("item_rmb_selected", i, get_local_mouse_position()); + emit_signal(SNAME("item_rmb_selected"), i, get_local_mouse_position()); } else if (/*select_mode==SELECT_SINGLE &&*/ mb->is_double_click()) { - emit_signal("item_activated", i); + emit_signal(SNAME("item_activated"), i); } } } @@ -634,13 +639,13 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { return; } if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { - emit_signal("rmb_clicked", mb->get_position()); + 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("nothing_selected"); + emit_signal(SNAME("nothing_selected")); } if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed()) { scroll_bar->set_value(scroll_bar->get_value() - scroll_bar->get_page() * mb->get_factor() / 8); @@ -661,7 +666,7 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { set_current(i); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { - emit_signal("item_selected", current); + emit_signal(SNAME("item_selected"), current); } break; @@ -676,7 +681,7 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { set_current(current - current_columns); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { - emit_signal("item_selected", current); + emit_signal(SNAME("item_selected"), current); } accept_event(); } @@ -691,7 +696,7 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { set_current(i); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { - emit_signal("item_selected", current); + emit_signal(SNAME("item_selected"), current); } break; } @@ -705,7 +710,7 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { set_current(current + current_columns); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { - emit_signal("item_selected", current); + emit_signal(SNAME("item_selected"), current); } accept_event(); } @@ -717,7 +722,7 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { set_current(current - current_columns * i); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { - emit_signal("item_selected", current); + emit_signal(SNAME("item_selected"), current); } accept_event(); break; @@ -731,7 +736,7 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { set_current(current + current_columns * i); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { - emit_signal("item_selected", current); + emit_signal(SNAME("item_selected"), current); } accept_event(); @@ -745,7 +750,7 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { set_current(current - 1); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { - emit_signal("item_selected", current); + emit_signal(SNAME("item_selected"), current); } accept_event(); } @@ -756,7 +761,7 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { set_current(current + 1); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { - emit_signal("item_selected", current); + emit_signal(SNAME("item_selected"), current); } accept_event(); } @@ -766,17 +771,17 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { if (current >= 0 && current < items.size()) { if (items[current].selectable && !items[current].disabled && !items[current].selected) { select(current, false); - emit_signal("multi_selected", current, true); + emit_signal(SNAME("multi_selected"), current, true); } else if (items[current].selected) { deselect(current); - emit_signal("multi_selected", current, false); + emit_signal(SNAME("multi_selected"), current, false); } } } else if (p_event->is_action("ui_accept")) { search_string = ""; //any mousepress cancels if (current >= 0 && current < items.size()) { - emit_signal("item_activated", current); + emit_signal(SNAME("item_activated"), current); } } else { Ref<InputEventKey> k = p_event; @@ -812,7 +817,7 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { set_current(i); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { - emit_signal("item_selected", current); + emit_signal(SNAME("item_selected"), current); } break; } @@ -867,7 +872,7 @@ void ItemList::_notification(int p_what) { } if (p_what == NOTIFICATION_DRAW) { - Ref<StyleBox> bg = get_theme_stylebox("bg"); + Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg")); int mw = scroll_bar->get_minimum_size().x; scroll_bar->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -mw); @@ -884,24 +889,24 @@ void ItemList::_notification(int p_what) { draw_style_box(bg, Rect2(Point2(), size)); - int hseparation = get_theme_constant("hseparation"); - int vseparation = get_theme_constant("vseparation"); - int icon_margin = get_theme_constant("icon_margin"); - int line_separation = get_theme_constant("line_separation"); - Color font_outline_color = get_theme_color("font_outline_color"); - int outline_size = get_theme_constant("outline_size"); + int hseparation = get_theme_constant(SNAME("hseparation")); + int vseparation = get_theme_constant(SNAME("vseparation")); + 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")); + int outline_size = get_theme_constant(SNAME("outline_size")); - Ref<StyleBox> sbsel = has_focus() ? get_theme_stylebox("selected_focus") : get_theme_stylebox("selected"); - Ref<StyleBox> cursor = has_focus() ? get_theme_stylebox("cursor") : get_theme_stylebox("cursor_unfocused"); + Ref<StyleBox> sbsel = has_focus() ? get_theme_stylebox(SNAME("selected_focus")) : get_theme_stylebox(SNAME("selected")); + Ref<StyleBox> cursor = has_focus() ? get_theme_stylebox(SNAME("cursor")) : get_theme_stylebox(SNAME("cursor_unfocused")); bool rtl = is_layout_rtl(); - Color guide_color = get_theme_color("guide_color"); - Color font_color = get_theme_color("font_color"); - Color font_selected_color = get_theme_color("font_selected_color"); + Color guide_color = get_theme_color(SNAME("guide_color")); + Color font_color = get_theme_color(SNAME("font_color")); + Color font_selected_color = get_theme_color(SNAME("font_selected_color")); if (has_focus()) { RenderingServer::get_singleton()->canvas_item_add_clip_ignore(get_canvas_item(), true); - draw_style_box(get_theme_stylebox("bg_focus"), Rect2(Point2(), size)); + draw_style_box(get_theme_stylebox(SNAME("bg_focus")), Rect2(Point2(), size)); RenderingServer::get_singleton()->canvas_item_add_clip_ignore(get_canvas_item(), false); } @@ -928,8 +933,14 @@ void ItemList::_notification(int p_what) { } if (items[i].text != "") { + int max_width = -1; + if (fixed_column_width) { + max_width = fixed_column_width; + } else if (same_column_width) { + max_width = items[i].rect_cache.size.x; + } + items.write[i].text_buf->set_width(max_width); Size2 s = items[i].text_buf->get_size(); - //s.width=MIN(s.width,fixed_column_width); if (icon_mode == ICON_MODE_TOP) { minsize.x = MAX(minsize.x, s.width); @@ -1137,11 +1148,8 @@ void ItemList::_notification(int p_what) { if (icon_mode == ICON_MODE_TOP) { pos.x += Math::floor((items[i].rect_cache.size.width - icon_size.width) / 2); - pos.y += MIN( - Math::floor((items[i].rect_cache.size.height - icon_size.height) / 2), - items[i].rect_cache.size.height - items[i].min_rect_cache.size.height); - text_ofs.y = icon_size.height + icon_margin; - text_ofs.y += items[i].rect_cache.size.height - items[i].min_rect_cache.size.height; + pos.y += icon_margin; + text_ofs.y = icon_size.height + icon_margin * 2; } else { pos.y += Math::floor((items[i].rect_cache.size.height - icon_size.height) / 2); text_ofs.x = icon_size.width + icon_margin; @@ -1208,7 +1216,6 @@ void ItemList::_notification(int p_what) { text_ofs.x = size.width - text_ofs.x - max_len; } - items.write[i].text_buf->set_width(max_len); items.write[i].text_buf->set_align(HALIGN_CENTER); if (outline_size > 0 && font_outline_color.a > 0) { @@ -1299,7 +1306,7 @@ void ItemList::_scroll_changed(double) { int ItemList::get_item_at_position(const Point2 &p_pos, bool p_exact) const { Vector2 pos = p_pos; - Ref<StyleBox> bg = get_theme_stylebox("bg"); + Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg")); pos -= bg->get_offset(); pos.y += scroll_bar->get_value(); @@ -1337,7 +1344,7 @@ bool ItemList::is_pos_at_end_of_items(const Point2 &p_pos) const { } Vector2 pos = p_pos; - Ref<StyleBox> bg = get_theme_stylebox("bg"); + Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg")); pos -= bg->get_offset(); pos.y += scroll_bar->get_value(); @@ -1486,6 +1493,21 @@ bool ItemList::has_auto_height() const { return auto_height; } +void ItemList::set_text_overrun_behavior(TextParagraph::OverrunBehavior p_behavior) { + if (text_overrun_behavior != p_behavior) { + text_overrun_behavior = p_behavior; + for (int i = 0; i < items.size(); i++) { + items.write[i].text_buf->set_text_overrun_behavior(p_behavior); + } + shape_changed = true; + update(); + } +} + +TextParagraph::OverrunBehavior ItemList::get_text_overrun_behavior() const { + return text_overrun_behavior; +} + void ItemList::_bind_methods() { ClassDB::bind_method(D_METHOD("add_item", "text", "icon", "selectable"), &ItemList::add_item, DEFVAL(Variant()), DEFVAL(true)); ClassDB::bind_method(D_METHOD("add_icon_item", "icon", "selectable"), &ItemList::add_icon_item, DEFVAL(true)); @@ -1592,11 +1614,12 @@ void ItemList::_bind_methods() { ClassDB::bind_method(D_METHOD("get_v_scroll"), &ItemList::get_v_scroll); - ClassDB::bind_method(D_METHOD("_gui_input"), &ItemList::_gui_input); - ClassDB::bind_method(D_METHOD("_set_items"), &ItemList::_set_items); ClassDB::bind_method(D_METHOD("_get_items"), &ItemList::_get_items); + ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "overrun_behavior"), &ItemList::set_text_overrun_behavior); + ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &ItemList::get_text_overrun_behavior); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "items", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_items", "_get_items"); ADD_PROPERTY(PropertyInfo(Variant::INT, "select_mode", PROPERTY_HINT_ENUM, "Single,Multi"), "set_select_mode", "get_select_mode"); @@ -1604,6 +1627,7 @@ void ItemList::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_rmb_select"), "set_allow_rmb_select", "get_allow_rmb_select"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_text_lines", PROPERTY_HINT_RANGE, "1,10,1,or_greater"), "set_max_text_lines", "get_max_text_lines"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_height"), "set_auto_height", "has_auto_height"); + 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_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"); @@ -1632,7 +1656,7 @@ void ItemList::_bind_methods() { ItemList::ItemList() { scroll_bar = memnew(VScrollBar); - add_child(scroll_bar); + add_child(scroll_bar, false, INTERNAL_MODE_FRONT); scroll_bar->connect("value_changed", callable_mp(this, &ItemList::_scroll_changed)); diff --git a/scene/gui/item_list.h b/scene/gui/item_list.h index 86a0174a20..148fa7ba9f 100644 --- a/scene/gui/item_list.h +++ b/scene/gui/item_list.h @@ -95,6 +95,7 @@ private: SelectMode select_mode = SELECT_SINGLE; IconMode icon_mode = ICON_MODE_LEFT; VScrollBar *scroll_bar; + TextParagraph::OverrunBehavior text_overrun_behavior = TextParagraph::OVERRUN_NO_TRIMMING; uint64_t search_time_msec = 0; String search_string; @@ -122,7 +123,6 @@ private: void _set_items(const Array &p_items); void _scroll_changed(double); - void _gui_input(const Ref<InputEvent> &p_event); void _shape(int p_idx); protected: @@ -130,6 +130,8 @@ protected: static void _bind_methods(); public: + virtual void gui_input(const Ref<InputEvent> &p_event) override; + int add_item(const String &p_item, const Ref<Texture2D> &p_texture = Ref<Texture2D>(), bool p_selectable = true); int add_icon_item(const Ref<Texture2D> &p_item, bool p_selectable = true); @@ -182,6 +184,9 @@ 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 select(int p_idx, bool p_single = true); void deselect(int p_idx); void deselect_all(); diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 6580d794d1..5600816b2d 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -36,20 +36,20 @@ #include "servers/text_server.h" -void Label::set_autowrap(bool p_autowrap) { - if (autowrap != p_autowrap) { - autowrap = p_autowrap; +void Label::set_autowrap_mode(Label::AutowrapMode p_mode) { + if (autowrap_mode != p_mode) { + autowrap_mode = p_mode; lines_dirty = true; } update(); - if (clip) { + if (clip || overrun_behavior != OVERRUN_NO_TRIMMING) { minimum_size_changed(); } } -bool Label::has_autowrap() const { - return autowrap; +Label::AutowrapMode Label::get_autowrap_mode() const { + return autowrap_mode; } void Label::set_uppercase(bool p_uppercase) { @@ -64,22 +64,22 @@ bool Label::is_uppercase() const { } int Label::get_line_height(int p_line) const { - Ref<Font> font = get_theme_font("font"); + Ref<Font> 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(Font::SPACING_TOP) + font->get_spacing(Font::SPACING_BOTTOM); + return TS->shaped_text_get_size(lines_rid[p_line]).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM); } 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(Font::SPACING_TOP) + font->get_spacing(Font::SPACING_BOTTOM); + h = MAX(h, TS->shaped_text_get_size(lines_rid[i]).y) + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM); } return h; } else { - return font->get_height(get_theme_font_size("font_size")); + return font->get_height(get_theme_font_size(SNAME("font_size"))); } } void Label::_shape() { - Ref<StyleBox> style = get_theme_stylebox("normal", "Label"); + Ref<StyleBox> style = get_theme_stylebox(SNAME("normal"), SNAME("Label")); int width = (get_size().width - style->get_minimum_size().width); if (dirty) { @@ -89,20 +89,39 @@ void Label::_shape() { } else { TS->shaped_text_set_direction(text_rid, (TextServer::Direction)text_direction); } - TS->shaped_text_add_string(text_rid, (uppercase) ? xl_text.to_upper() : xl_text, get_theme_font("font")->get_rids(), get_theme_font_size("font_size"), opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); + const Ref<Font> &font = get_theme_font(SNAME("font")); + int font_size = get_theme_font_size(SNAME("font_size")); + ERR_FAIL_COND(font.is_null()); + TS->shaped_text_add_string(text_rid, (uppercase) ? xl_text.to_upper() : xl_text, font->get_rids(), font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); TS->shaped_text_set_bidi_override(text_rid, structured_text_parser(st_parser, st_args, xl_text)); dirty = false; lines_dirty = true; } + if (lines_dirty) { for (int i = 0; i < lines_rid.size(); i++) { TS->free(lines_rid[i]); } lines_rid.clear(); - Vector<Vector2i> lines = TS->shaped_text_get_line_breaks(text_rid, width, 0, (autowrap) ? (TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND) : TextServer::BREAK_MANDATORY); - for (int i = 0; i < lines.size(); i++) { - RID line = TS->shaped_text_substr(text_rid, lines[i].x, lines[i].y - lines[i].x); + uint8_t autowrap_flags = TextServer::BREAK_MANDATORY; + switch (autowrap_mode) { + case AUTOWRAP_WORD_SMART: + autowrap_flags = TextServer::BREAK_WORD_BOUND_ADAPTIVE | TextServer::BREAK_MANDATORY; + break; + case AUTOWRAP_WORD: + autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_MANDATORY; + break; + case AUTOWRAP_ARBITRARY: + autowrap_flags = TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_MANDATORY; + break; + case AUTOWRAP_OFF: + break; + } + Vector<Vector2i> line_breaks = TS->shaped_text_get_line_breaks(text_rid, width, 0, autowrap_flags); + + for (int i = 0; i < line_breaks.size(); i++) { + RID line = TS->shaped_text_substr(text_rid, line_breaks[i].x, line_breaks[i].y - line_breaks[i].x); lines_rid.push_back(line); } } @@ -111,7 +130,8 @@ void Label::_shape() { minsize = Size2(1, get_line_height()); return; } - if (!autowrap) { + + if (autowrap_mode == 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) { @@ -120,10 +140,61 @@ void Label::_shape() { } } - if (lines_dirty) { // Fill after min_size calculation. - if (align == ALIGN_FILL) { + if (lines_dirty) { + uint8_t overrun_flags = 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; + break; + case OVERRUN_TRIM_ELLIPSIS: + overrun_flags |= TextServer::OVERRUN_TRIM; + overrun_flags |= TextServer::OVERRUN_ADD_ELLIPSIS; + break; + case OVERRUN_TRIM_WORD: + overrun_flags |= TextServer::OVERRUN_TRIM; + overrun_flags |= TextServer::OVERRUN_TRIM_WORD_ONLY; + break; + case OVERRUN_TRIM_CHAR: + overrun_flags |= TextServer::OVERRUN_TRIM; + break; + case OVERRUN_NO_TRIMMING: + break; + } + + // Fill after min_size calculation. + + if (autowrap_mode != 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; + } + if (align == ALIGN_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); + } else if (i == (visible_lines - 1)) { + TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], width, overrun_flags); + } + } + + } else if (lines_hidden) { + TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], width, overrun_flags); + } + + } else { + // Autowrap disabled. for (int i = 0; i < lines_rid.size(); i++) { - TS->shaped_text_fit_to_width(lines_rid.write[i], width); + if (align == ALIGN_FILL) { + TS->shaped_text_fit_to_width(lines_rid[i], width); + overrun_flags |= 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 { + TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags); + } } } lines_dirty = false; @@ -131,15 +202,15 @@ void Label::_shape() { _update_visible(); - if (!autowrap || !clip) { + if (autowrap_mode == AUTOWRAP_OFF || !clip || overrun_behavior == OVERRUN_NO_TRIMMING) { minimum_size_changed(); } } void Label::_update_visible() { - int line_spacing = get_theme_constant("line_spacing", "Label"); - Ref<StyleBox> style = get_theme_stylebox("normal", "Label"); - Ref<Font> font = get_theme_font("font"); + int 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) { @@ -149,18 +220,42 @@ 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(Font::SPACING_TOP) + font->get_spacing(Font::SPACING_BOTTOM) + line_spacing; + 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; if (minsize.height > (get_size().height - style->get_minimum_size().height + line_spacing)) { break; } } } +inline void draw_glyph(const TextServer::Glyph &p_gl, const RID &p_canvas, const Color &p_font_color, const Vector2 &p_ofs) { + if (p_gl.font_rid != RID()) { + TS->font_draw_glyph(p_gl.font_rid, p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_color); + } else { + TS->draw_hex_code_box(p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_color); + } +} + +inline void draw_glyph_outline(const TextServer::Glyph &p_gl, const RID &p_canvas, const Color &p_font_color, const Color &p_font_shadow_color, const Color &p_font_outline_color, const int &p_shadow_outline_size, const int &p_outline_size, const Vector2 &p_ofs, const Vector2 &shadow_ofs) { + if (p_gl.font_rid != RID()) { + if (p_font_shadow_color.a > 0) { + TS->font_draw_glyph(p_gl.font_rid, p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + shadow_ofs, p_gl.index, p_font_shadow_color); + if (p_shadow_outline_size > 0) { + TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_shadow_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + Vector2(-shadow_ofs.x, shadow_ofs.y), p_gl.index, p_font_shadow_color); + TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_shadow_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + Vector2(shadow_ofs.x, -shadow_ofs.y), p_gl.index, p_font_shadow_color); + TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_shadow_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + Vector2(-shadow_ofs.x, -shadow_ofs.y), p_gl.index, p_font_shadow_color); + } + } + if (p_font_outline_color.a != 0.0 && p_outline_size > 0) { + TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_outline_color); + } + } +} + void Label::_notification(int p_what) { if (p_what == NOTIFICATION_TRANSLATION_CHANGED) { - String new_text = tr(text); + String new_text = atr(text); if (new_text == xl_text) { - return; //nothing new + return; // Nothing new. } xl_text = new_text; dirty = true; @@ -181,16 +276,16 @@ void Label::_notification(int p_what) { Size2 string_size; Size2 size = get_size(); - Ref<StyleBox> style = get_theme_stylebox("normal"); - Ref<Font> font = get_theme_font("font"); - Color font_color = get_theme_color("font_color"); - Color font_shadow_color = get_theme_color("font_shadow_color"); - Point2 shadow_ofs(get_theme_constant("shadow_offset_x"), get_theme_constant("shadow_offset_y")); - int line_spacing = get_theme_constant("line_spacing"); - Color font_outline_color = get_theme_color("font_outline_color"); - int outline_size = get_theme_constant("outline_size"); - int shadow_outline_size = get_theme_constant("shadow_outline_size"); - bool rtl = is_layout_rtl(); + 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")); + bool rtl = TS->shaped_text_get_direction(text_rid); style->draw(ci, Rect2(Point2(0, 0), get_size())); @@ -199,7 +294,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(Font::SPACING_TOP) + font->get_spacing(Font::SPACING_BOTTOM) + line_spacing; + 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; if (total_h > (get_size().height - style->get_minimum_size().height + line_spacing)) { break; } @@ -215,7 +310,7 @@ void Label::_notification(int p_what) { // Get real total height. 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(Font::SPACING_TOP) + font->get_spacing(Font::SPACING_BOTTOM) + line_spacing; + 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 += style->get_margin(SIDE_TOP) + style->get_margin(SIDE_BOTTOM); @@ -223,7 +318,7 @@ void Label::_notification(int p_what) { if (lines_visible > 0) { switch (valign) { case VALIGN_TOP: { - //nothing + // Nothing. } break; case VALIGN_CENTER: { vbegin = (size.y - (total_h - line_spacing)) / 2; @@ -268,84 +363,143 @@ void Label::_notification(int p_what) { Vector2 ofs; ofs.y = style->get_offset().y + vbegin; 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(Font::SPACING_TOP); + ofs.y += TS->shaped_text_get_ascent(lines_rid[i]) + font->get_spacing(TextServer::SPACING_TOP); switch (align) { case ALIGN_FILL: - case ALIGN_LEFT: { - if (rtl) { - ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - TS->shaped_text_get_size(lines_rid[i]).x); + if (rtl && autowrap_mode != AUTOWRAP_OFF) { + ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width); } else { ofs.x = style->get_offset().x; } + break; + case ALIGN_LEFT: { + ofs.x = style->get_offset().x; } break; case ALIGN_CENTER: { - ofs.x = int(size.width - TS->shaped_text_get_size(lines_rid[i]).x) / 2; + ofs.x = int(size.width - line_size.width) / 2; } break; case ALIGN_RIGHT: { - if (rtl) { - ofs.x = style->get_offset().x; - } else { - ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - TS->shaped_text_get_size(lines_rid[i]).x); - } + ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width); } break; } const Vector<TextServer::Glyph> visual = TS->shaped_text_get_glyphs(lines_rid[i]); const TextServer::Glyph *glyphs = visual.ptr(); int gl_size = visual.size(); + TextServer::TrimData trim_data = TS->shaped_text_get_trim_data(lines_rid[i]); + + // Draw outline. Note: Do not merge this into the single loop with the main text, to prevent overlaps. + if (font_shadow_color.a > 0 || (font_outline_color.a != 0.0 && outline_size > 0)) { + Vector2 offset = ofs; + // Draw RTL ellipsis string when necessary. + if (rtl && trim_data.ellipsis_pos >= 0) { + for (int gl_idx = trim_data.ellipsis_glyph_buf.size() - 1; gl_idx >= 0; gl_idx--) { + for (int j = 0; j < trim_data.ellipsis_glyph_buf[gl_idx].repeat; j++) { + //Draw glyph outlines and shadow. + draw_glyph_outline(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs); + offset.x += trim_data.ellipsis_glyph_buf[gl_idx].advance; + } + } + } - float x = ofs.x; - int outlines_drawn = glyhps_drawn; - for (int j = 0; j < gl_size; j++) { - for (int k = 0; k < glyphs[j].repeat; k++) { - if (glyphs[j].font_rid != RID()) { - if (font_shadow_color.a > 0) { - TS->font_draw_glyph(glyphs[j].font_rid, ci, glyphs[j].font_size, ofs + Vector2(glyphs[j].x_off, glyphs[j].y_off) + shadow_ofs, glyphs[j].index, font_shadow_color); - if (shadow_outline_size > 0) { - //draw shadow - TS->font_draw_glyph_outline(glyphs[j].font_rid, ci, glyphs[j].font_size, shadow_outline_size, ofs + Vector2(glyphs[j].x_off, glyphs[j].y_off) + Vector2(-shadow_ofs.x, shadow_ofs.y), glyphs[j].index, font_shadow_color); - TS->font_draw_glyph_outline(glyphs[j].font_rid, ci, glyphs[j].font_size, shadow_outline_size, ofs + Vector2(glyphs[j].x_off, glyphs[j].y_off) + Vector2(shadow_ofs.x, -shadow_ofs.y), glyphs[j].index, font_shadow_color); - TS->font_draw_glyph_outline(glyphs[j].font_rid, ci, glyphs[j].font_size, shadow_outline_size, ofs + Vector2(glyphs[j].x_off, glyphs[j].y_off) + Vector2(-shadow_ofs.x, -shadow_ofs.y), glyphs[j].index, font_shadow_color); + // Draw main text. + for (int j = 0; j < gl_size; j++) { + for (int k = 0; k < glyphs[j].repeat; k++) { + if (visible_glyphs != -1) { + if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + if (glyhps_drawn >= visible_glyphs) { + return; + } } } - if (font_outline_color.a != 0.0 && outline_size > 0) { - TS->font_draw_glyph_outline(glyphs[j].font_rid, ci, glyphs[j].font_size, outline_size, ofs + Vector2(glyphs[j].x_off, glyphs[j].y_off), glyphs[j].index, font_outline_color); + + // Trim when necessary. + if (trim_data.trim_pos >= 0) { + if (rtl) { + if (j < trim_data.trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + continue; + } + } else { + if (j >= trim_data.trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + break; + } + } } + + // Draw glyph outlines and shadow. + draw_glyph_outline(glyphs[j], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs); + offset.x += glyphs[j].advance; + glyhps_drawn++; } - ofs.x += glyphs[j].advance; } - if (visible_glyphs != -1) { - if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { - outlines_drawn++; - if (outlines_drawn >= visible_glyphs) { - break; + // Draw LTR ellipsis string when necessary. + if (!rtl && trim_data.ellipsis_pos >= 0) { + for (int gl_idx = 0; gl_idx < trim_data.ellipsis_glyph_buf.size(); gl_idx++) { + for (int j = 0; j < trim_data.ellipsis_glyph_buf[gl_idx].repeat; j++) { + //Draw glyph outlines and shadow. + draw_glyph_outline(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs); + offset.x += trim_data.ellipsis_glyph_buf[gl_idx].advance; } } } } - ofs.x = x; + // Draw main text. Note: Do not merge this into the single loop with the outline, to prevent overlaps. + + // Draw RTL ellipsis string when necessary. + if (rtl && trim_data.ellipsis_pos >= 0) { + for (int gl_idx = trim_data.ellipsis_glyph_buf.size() - 1; gl_idx >= 0; gl_idx--) { + for (int j = 0; j < trim_data.ellipsis_glyph_buf[gl_idx].repeat; j++) { + //Draw glyph outlines and shadow. + draw_glyph(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, ofs); + ofs.x += trim_data.ellipsis_glyph_buf[gl_idx].advance; + } + } + } + + // Draw main text. for (int j = 0; j < gl_size; j++) { for (int k = 0; k < glyphs[j].repeat; k++) { - if (glyphs[j].font_rid != RID()) { - TS->font_draw_glyph(glyphs[j].font_rid, ci, glyphs[j].font_size, ofs + Vector2(glyphs[j].x_off, glyphs[j].y_off), glyphs[j].index, font_color); - } else if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { - TS->draw_hex_code_box(ci, glyphs[j].font_size, ofs + Vector2(glyphs[j].x_off, glyphs[j].y_off), glyphs[j].index, font_color); + if (visible_glyphs != -1) { + if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + if (glyhps_drawn >= visible_glyphs) { + return; + } + } } + + // Trim when necessary. + if (trim_data.trim_pos >= 0) { + if (rtl) { + if (j < trim_data.trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + continue; + } + } else { + if (j >= trim_data.trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + break; + } + } + } + + // Draw glyph outlines and shadow. + draw_glyph(glyphs[j], ci, font_color, ofs); ofs.x += glyphs[j].advance; + glyhps_drawn++; } - if (visible_glyphs != -1) { - if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { - glyhps_drawn++; - if (glyhps_drawn >= visible_glyphs) { - return; - } + } + // Draw LTR ellipsis string when necessary. + if (!rtl && trim_data.ellipsis_pos >= 0) { + for (int gl_idx = 0; gl_idx < trim_data.ellipsis_glyph_buf.size(); gl_idx++) { + for (int j = 0; j < trim_data.ellipsis_glyph_buf[gl_idx].repeat; j++) { + //Draw glyph outlines and shadow. + draw_glyph(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, ofs); + ofs.x += trim_data.ellipsis_glyph_buf[gl_idx].advance; } } } - - ofs.y += TS->shaped_text_get_descent(lines_rid[i]) + vsep + line_spacing + font->get_spacing(Font::SPACING_BOTTOM); + ofs.y += TS->shaped_text_get_descent(lines_rid[i]) + vsep + line_spacing + font->get_spacing(TextServer::SPACING_BOTTOM); } } @@ -366,17 +520,16 @@ Size2 Label::get_minimum_size() const { Size2 min_size = minsize; - Ref<Font> font = get_theme_font("font"); - min_size.height = MAX(min_size.height, font->get_height(get_theme_font_size("font_size")) + font->get_spacing(Font::SPACING_TOP) + font->get_spacing(Font::SPACING_BOTTOM)); + 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)); - Size2 min_style = get_theme_stylebox("normal")->get_minimum_size(); - if (autowrap) { - return Size2(1, clip ? 1 : min_size.height) + min_style; + 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; } else { - if (clip) { + if (clip || overrun_behavior != OVERRUN_NO_TRIMMING) { min_size.width = 1; } - return min_size + min_style; } } @@ -393,13 +546,13 @@ int Label::get_line_count() const { } int Label::get_visible_line_count() const { - Ref<Font> font = get_theme_font("font"); - Ref<StyleBox> style = get_theme_stylebox("normal"); - int line_spacing = get_theme_constant("line_spacing"); + 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 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(Font::SPACING_TOP) + font->get_spacing(Font::SPACING_BOTTOM) + line_spacing; + 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; if (total_h > (get_size().height - style->get_minimum_size().height + line_spacing)) { break; } @@ -447,7 +600,7 @@ void Label::set_text(const String &p_string) { return; } text = p_string; - xl_text = tr(p_string); + xl_text = atr(p_string); dirty = true; if (percent_visible < 1) { visible_chars = get_total_character_count() * percent_visible; @@ -536,6 +689,21 @@ bool Label::is_clipping_text() const { return clip; } +void Label::set_text_overrun_behavior(Label::OverrunBehavior p_behavior) { + if (overrun_behavior != p_behavior) { + overrun_behavior = p_behavior; + lines_dirty = true; + } + update(); + if (clip || overrun_behavior != OVERRUN_NO_TRIMMING) { + minimum_size_changed(); + } +} + +Label::OverrunBehavior Label::get_text_overrun_behavior() const { + return overrun_behavior; +} + String Label::get_text() const { return text; } @@ -547,6 +715,9 @@ void Label::set_visible_characters(int p_amount) { } else { percent_visible = 1.0; } + if (p_amount == -1) { + lines_dirty = true; + } update(); } @@ -558,7 +729,7 @@ void Label::set_percent_visible(float p_percent) { if (p_percent < 0 || p_percent >= 1) { visible_chars = -1; percent_visible = 1; - + lines_dirty = true; } else { visible_chars = get_total_character_count() * p_percent; percent_visible = p_percent; @@ -663,10 +834,12 @@ void Label::_bind_methods() { 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", "enable"), &Label::set_autowrap); - ClassDB::bind_method(D_METHOD("has_autowrap"), &Label::has_autowrap); + ClassDB::bind_method(D_METHOD("set_autowrap_mode", "autowrap_mode"), &Label::set_autowrap_mode); + ClassDB::bind_method(D_METHOD("get_autowrap_mode"), &Label::get_autowrap_mode); ClassDB::bind_method(D_METHOD("set_clip_text", "enable"), &Label::set_clip_text); ClassDB::bind_method(D_METHOD("is_clipping_text"), &Label::is_clipping_text); + ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "overrun_behavior"), &Label::set_text_overrun_behavior); + ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &Label::get_text_overrun_behavior); ClassDB::bind_method(D_METHOD("set_uppercase", "enable"), &Label::set_uppercase); ClassDB::bind_method(D_METHOD("is_uppercase"), &Label::is_uppercase); ClassDB::bind_method(D_METHOD("get_line_height", "line"), &Label::get_line_height, DEFVAL(-1)); @@ -696,13 +869,25 @@ void Label::_bind_methods() { BIND_ENUM_CONSTANT(VALIGN_BOTTOM); BIND_ENUM_CONSTANT(VALIGN_FILL); + 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); + 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"), "set_language", "get_language"); ADD_PROPERTY(PropertyInfo(Variant::INT, "align", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_align", "get_align"); ADD_PROPERTY(PropertyInfo(Variant::INT, "valign", PROPERTY_HINT_ENUM, "Top,Center,Bottom,Fill"), "set_valign", "get_valign"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autowrap"), "set_autowrap", "has_autowrap"); + 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::BOOL, "clip_text"), "set_clip_text", "is_clipping_text"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uppercase"), "set_uppercase", "is_uppercase"); ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters", PROPERTY_HINT_RANGE, "-1,128000,1", PROPERTY_USAGE_EDITOR), "set_visible_characters", "get_visible_characters"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "percent_visible", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_percent_visible", "get_percent_visible"); diff --git a/scene/gui/label.h b/scene/gui/label.h index 032b4112e1..8b48eb9670 100644 --- a/scene/gui/label.h +++ b/scene/gui/label.h @@ -51,13 +51,29 @@ public: VALIGN_FILL }; + 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, + }; + private: Align align = ALIGN_LEFT; VAlign valign = VALIGN_TOP; String text; String xl_text; - bool autowrap = false; + AutowrapMode autowrap_mode = AUTOWRAP_OFF; bool clip = false; + OverrunBehavior overrun_behavior = OVERRUN_NO_TRIMMING; Size2 minsize; bool uppercase = false; @@ -118,8 +134,8 @@ public: void set_structured_text_bidi_override_options(Array p_args); Array get_structured_text_bidi_override_options() const; - void set_autowrap(bool p_autowrap); - bool has_autowrap() const; + void set_autowrap_mode(AutowrapMode p_mode); + AutowrapMode get_autowrap_mode() const; void set_uppercase(bool p_uppercase); bool is_uppercase() const; @@ -131,6 +147,9 @@ 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_percent_visible(float p_percent); float get_percent_visible() const; @@ -150,5 +169,7 @@ public: VARIANT_ENUM_CAST(Label::Align); VARIANT_ENUM_CAST(Label::VAlign); +VARIANT_ENUM_CAST(Label::AutowrapMode); +VARIANT_ENUM_CAST(Label::OverrunBehavior); #endif diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 089893e63b..d9acbeb828 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -216,7 +216,7 @@ void LineEdit::_delete(bool p_word, bool p_all_to_right) { } } -void LineEdit::_gui_input(Ref<InputEvent> p_event) { +void LineEdit::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventMouseButton> b = p_event; @@ -227,9 +227,9 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { return; } if (b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_RIGHT && context_menu_enabled) { + _ensure_menu(); menu->set_position(get_screen_transform().xform(get_local_mouse_position())); menu->set_size(Vector2(1, 1)); - _generate_context_menu(); menu->popup(); grab_focus(); accept_event(); @@ -348,10 +348,10 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { if (context_menu_enabled) { if (k->is_action("ui_menu", true)) { - Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + get_theme_font("font")->get_height(get_theme_font_size("font_size"))) / 2); + _ensure_menu(); + Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + get_theme_font(SNAME("font"))->get_height(get_theme_font_size(SNAME("font_size")))) / 2); menu->set_position(get_global_transform().xform(pos)); menu->set_size(Vector2(1, 1)); - _generate_context_menu(); menu->popup(); menu->grab_focus(); } @@ -359,7 +359,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { // Default is ENTER and KP_ENTER. Cannot use ui_accept as default includes SPACE if (k->is_action("ui_text_submit", false)) { - emit_signal("text_submitted", text); + emit_signal(SNAME("text_submitted"), text); if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) { DisplayServer::get_singleton()->virtual_keyboard_hide(); } @@ -567,8 +567,8 @@ bool LineEdit::_is_over_clear_button(const Point2 &p_pos) const { if (!clear_button_enabled || !has_point(p_pos)) { return false; } - Ref<Texture2D> icon = Control::get_theme_icon("clear"); - int x_ofs = get_theme_stylebox("normal")->get_offset().x; + Ref<Texture2D> icon = Control::get_theme_icon(SNAME("clear")); + int x_ofs = get_theme_stylebox(SNAME("normal"))->get_offset().x; return p_pos.x > get_size().width - icon->get_width() - x_ofs; } @@ -577,8 +577,8 @@ void LineEdit::_notification(int p_what) { #ifdef TOOLS_ENABLED case NOTIFICATION_ENTER_TREE: { if (Engine::get_singleton()->is_editor_hint() && !get_tree()->is_node_being_edited(this)) { - set_caret_blink_enabled(EDITOR_DEF("text_editor/cursor/caret_blink", false)); - set_caret_blink_speed(EDITOR_DEF("text_editor/cursor/caret_blink_speed", 0.65)); + set_caret_blink_enabled(EDITOR_DEF("text_editor/appearance/caret/caret_blink", false)); + set_caret_blink_speed(EDITOR_DEF("text_editor/appearance/caret/caret_blink_speed", 0.65)); if (!EditorSettings::get_singleton()->is_connected("settings_changed", callable_mp(this, &LineEdit::_editor_settings_changed))) { EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &LineEdit::_editor_settings_changed)); @@ -597,7 +597,7 @@ void LineEdit::_notification(int p_what) { update(); } break; case NOTIFICATION_TRANSLATION_CHANGED: { - placeholder_translated = tr(placeholder); + placeholder_translated = atr(placeholder); _shape(); update(); } break; @@ -612,7 +612,7 @@ void LineEdit::_notification(int p_what) { update(); } break; case NOTIFICATION_DRAW: { - if ((!has_focus() && !menu->has_focus() && !caret_force_displayed) || !window_has_focus) { + if ((!has_focus() && !(menu && menu->has_focus()) && !caret_force_displayed) || !window_has_focus) { draw_caret = false; } @@ -625,23 +625,23 @@ void LineEdit::_notification(int p_what) { RID ci = get_canvas_item(); - Ref<StyleBox> style = get_theme_stylebox("normal"); + Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); if (!is_editable()) { - style = get_theme_stylebox("read_only"); + style = get_theme_stylebox(SNAME("read_only")); draw_caret = false; } - Ref<Font> font = get_theme_font("font"); + Ref<Font> font = get_theme_font(SNAME("font")); style->draw(ci, Rect2(Point2(), size)); if (has_focus()) { - get_theme_stylebox("focus")->draw(ci, Rect2(Point2(), size)); + get_theme_stylebox(SNAME("focus"))->draw(ci, Rect2(Point2(), size)); } 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(Font::SPACING_TOP) + font->get_spacing(Font::SPACING_BOTTOM); + float text_height = TS->shaped_text_get_size(text_rid).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM); switch (align) { case ALIGN_FILL: @@ -673,10 +673,10 @@ void LineEdit::_notification(int p_what) { int y_area = height - style->get_minimum_size().height; int y_ofs = style->get_offset().y + (y_area - text_height) / 2; - Color selection_color = get_theme_color("selection_color"); - Color font_color = is_editable() ? get_theme_color("font_color") : get_theme_color("font_uneditable_color"); - Color font_selected_color = get_theme_color("font_selected_color"); - Color caret_color = get_theme_color("caret_color"); + Color selection_color = get_theme_color(SNAME("selection_color")); + Color font_color = get_theme_color(is_editable() ? SNAME("font_color") : SNAME("font_uneditable_color")); + Color font_selected_color = get_theme_color(SNAME("font_selected_color")); + Color caret_color = get_theme_color(SNAME("caret_color")); // Draw placeholder color. if (using_placeholder) { @@ -685,13 +685,13 @@ void LineEdit::_notification(int p_what) { 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("clear") : right_icon; + Ref<Texture2D> r_icon = display_clear_icon ? Control::get_theme_icon(SNAME("clear")) : right_icon; Color color_icon(1, 1, 1, !is_editable() ? .5 * .9 : .9); if (display_clear_icon) { if (clear_button_status.press_attempt && clear_button_status.pressing_inside) { - color_icon = get_theme_color("clear_button_color_pressed"); + color_icon = get_theme_color(SNAME("clear_button_color_pressed")); } else { - color_icon = get_theme_color("clear_button_color"); + color_icon = get_theme_color(SNAME("clear_button_color")); } } @@ -738,8 +738,8 @@ void LineEdit::_notification(int p_what) { // Draw text. ofs.y += TS->shaped_text_get_ascent(text_rid); - Color font_outline_color = get_theme_color("font_outline_color"); - int outline_size = get_theme_constant("outline_size"); + Color font_outline_color = get_theme_color(SNAME("font_outline_color")); + int outline_size = get_theme_constant(SNAME("outline_size")); if (outline_size > 0 && font_outline_color.a > 0) { Vector2 oofs = ofs; for (int i = 0; i < gl_size; i++) { @@ -784,7 +784,7 @@ void LineEdit::_notification(int p_what) { if (l_caret == Rect2() && t_caret == Rect2()) { // No carets, add one at the start. - int h = get_theme_font("font")->get_height(get_theme_font_size("font_size")); + int h = get_theme_font(SNAME("font"))->get_height(get_theme_font_size(SNAME("font_size"))); int y = style->get_offset().y + (y_area - h) / 2; if (rtl) { l_dir = TextServer::DIRECTION_RTL; @@ -946,6 +946,17 @@ void LineEdit::paste_text() { } } +bool LineEdit::has_undo() const { + if (undo_stack_pos == nullptr) { + return undo_stack.size() > 1; + } + return undo_stack_pos != undo_stack.front(); +} + +bool LineEdit::has_redo() const { + return undo_stack_pos != nullptr && undo_stack_pos != undo_stack.back(); +} + void LineEdit::undo() { if (!editable) { return; @@ -1006,7 +1017,7 @@ void LineEdit::shift_selection_check_post(bool p_shift) { } void LineEdit::set_caret_at_pixel_pos(int p_x) { - Ref<StyleBox> style = get_theme_stylebox("normal"); + Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); bool rtl = is_layout_rtl(); int x_ofs = 0; @@ -1039,7 +1050,7 @@ void LineEdit::set_caret_at_pixel_pos(int p_x) { 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("clear") : right_icon; + Ref<Texture2D> r_icon = display_clear_icon ? Control::get_theme_icon(SNAME("clear")) : right_icon; if (align == ALIGN_CENTER) { if (scroll_offset == 0) { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - text_width - r_icon->get_width() - style->get_margin(SIDE_RIGHT) * 2) / 2); @@ -1054,7 +1065,7 @@ void LineEdit::set_caret_at_pixel_pos(int p_x) { } Vector2i LineEdit::get_caret_pixel_pos() { - Ref<StyleBox> style = get_theme_stylebox("normal"); + Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); bool rtl = is_layout_rtl(); int x_ofs = 0; @@ -1087,7 +1098,7 @@ Vector2i LineEdit::get_caret_pixel_pos() { 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("clear") : right_icon; + Ref<Texture2D> r_icon = display_clear_icon ? Control::get_theme_icon(SNAME("clear")) : right_icon; if (align == ALIGN_CENTER) { if (scroll_offset == 0) { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - text_width - r_icon->get_width() - style->get_margin(SIDE_RIGHT) * 2) / 2); @@ -1252,10 +1263,12 @@ void LineEdit::set_text_direction(Control::TextDirection p_text_direction) { } _shape(); - menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_INHERITED), text_direction == TEXT_DIRECTION_INHERITED); - menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_AUTO), text_direction == TEXT_DIRECTION_AUTO); - menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_LTR), text_direction == TEXT_DIRECTION_LTR); - menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_RTL), text_direction == TEXT_DIRECTION_RTL); + if (menu_dir) { + menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_INHERITED), text_direction == TEXT_DIRECTION_INHERITED); + menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_AUTO), text_direction == TEXT_DIRECTION_AUTO); + menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_LTR), text_direction == TEXT_DIRECTION_LTR); + menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_RTL), text_direction == TEXT_DIRECTION_RTL); + } update(); } } @@ -1302,7 +1315,9 @@ String LineEdit::get_language() const { void LineEdit::set_draw_control_chars(bool p_draw_control_chars) { if (draw_control_chars != p_draw_control_chars) { draw_control_chars = p_draw_control_chars; - menu->set_item_checked(menu->get_item_index(MENU_DISPLAY_UCC), draw_control_chars); + if (menu && menu->get_item_index(MENU_DISPLAY_UCC) >= 0) { + menu->set_item_checked(menu->get_item_index(MENU_DISPLAY_UCC), draw_control_chars); + } _shape(); update(); } @@ -1360,7 +1375,7 @@ String LineEdit::get_text() const { void LineEdit::set_placeholder(String p_text) { placeholder = p_text; - placeholder_translated = tr(placeholder); + placeholder_translated = atr(placeholder); _shape(); update(); } @@ -1396,7 +1411,7 @@ void LineEdit::set_caret_column(int p_column) { return; } - Ref<StyleBox> style = get_theme_stylebox("normal"); + Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); bool rtl = is_layout_rtl(); int x_ofs = 0; @@ -1430,7 +1445,7 @@ void LineEdit::set_caret_column(int p_column) { 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("clear") : right_icon; + Ref<Texture2D> r_icon = display_clear_icon ? Control::get_theme_icon(SNAME("clear")) : right_icon; if (align == ALIGN_CENTER) { if (scroll_offset == 0) { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - text_width - r_icon->get_width() - style->get_margin(SIDE_RIGHT) * 2) / 2); @@ -1470,19 +1485,23 @@ int LineEdit::get_scroll_offset() const { } void LineEdit::insert_text_at_caret(String p_text) { - if ((max_length <= 0) || (text.length() + p_text.length() <= max_length)) { - String pre = text.substr(0, caret_column); - String post = text.substr(caret_column, text.length() - caret_column); - text = pre + p_text + post; - _shape(); - TextServer::Direction dir = TS->shaped_text_get_dominant_direciton_in_range(text_rid, caret_column, caret_column + p_text.length()); - if (dir != TextServer::DIRECTION_AUTO) { - input_direction = (TextDirection)dir; + if (max_length > 0) { + // Truncate text to append to fit in max_length, if needed. + int available_chars = max_length - text.length(); + if (p_text.length() > available_chars) { + emit_signal(SNAME("text_change_rejected"), p_text.substr(available_chars)); + p_text = p_text.substr(0, available_chars); } - set_caret_column(caret_column + p_text.length()); - } else { - emit_signal("text_change_rejected"); } + String pre = text.substr(0, caret_column); + String post = text.substr(caret_column, text.length() - caret_column); + text = pre + p_text + post; + _shape(); + TextServer::Direction dir = TS->shaped_text_get_dominant_direciton_in_range(text_rid, caret_column, caret_column + p_text.length()); + if (dir != TextServer::DIRECTION_AUTO) { + input_direction = (TextDirection)dir; + } + set_caret_column(caret_column + p_text.length()); } void LineEdit::clear_internal() { @@ -1497,28 +1516,28 @@ void LineEdit::clear_internal() { } Size2 LineEdit::get_minimum_size() const { - Ref<StyleBox> style = get_theme_stylebox("normal"); - Ref<Font> font = get_theme_font("font"); - int font_size = get_theme_font_size("font_size"); + Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); + Ref<Font> font = get_theme_font(SNAME("font")); + int font_size = get_theme_font_size(SNAME("font_size")); Size2 min_size; // Minimum size of text. int em_space_size = font->get_char_size('M', 0, font_size).x; - min_size.width = get_theme_constant("minimum_character_width") * em_space_size; + min_size.width = get_theme_constant(SNAME("minimum_character_width")) * em_space_size; if (expand_to_text_length) { // Add a space because some fonts are too exact, and because caret needs a bit more when at the end. 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(Font::SPACING_TOP) + font->get_spacing(Font::SPACING_BOTTOM), font->get_height(font_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)); // 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("clear") : right_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()); } @@ -1592,7 +1611,6 @@ void LineEdit::set_editable(bool p_editable) { } editable = p_editable; - _generate_context_menu(); minimum_size_changed(); update(); @@ -1806,14 +1824,19 @@ bool LineEdit::is_context_menu_enabled() { return context_menu_enabled; } +bool LineEdit::is_menu_visible() const { + return menu && menu->is_visible(); +} + PopupMenu *LineEdit::get_menu() const { + const_cast<LineEdit *>(this)->_ensure_menu(); return menu; } void LineEdit::_editor_settings_changed() { #ifdef TOOLS_ENABLED - set_caret_blink_enabled(EDITOR_DEF("text_editor/cursor/caret_blink", false)); - set_caret_blink_speed(EDITOR_DEF("text_editor/cursor/caret_blink_speed", 0.65)); + set_caret_blink_enabled(EDITOR_DEF("text_editor/appearance/caret/caret_blink", false)); + set_caret_blink_speed(EDITOR_DEF("text_editor/appearance/caret/caret_blink_speed", 0.65)); #endif } @@ -1843,8 +1866,6 @@ bool LineEdit::is_clear_button_enabled() const { void LineEdit::set_shortcut_keys_enabled(bool p_enabled) { shortcut_keys_enabled = p_enabled; - - _generate_context_menu(); } bool LineEdit::is_shortcut_keys_enabled() const { @@ -1865,8 +1886,6 @@ void LineEdit::set_selecting_enabled(bool p_enabled) { if (!selecting_enabled) { deselect(); } - - _generate_context_menu(); } bool LineEdit::is_selecting_enabled() const { @@ -1893,7 +1912,7 @@ void LineEdit::_text_changed() { } void LineEdit::_emit_text_change() { - emit_signal("text_changed", text); + emit_signal(SNAME("text_changed"), text); text_changed_dirty = false; } @@ -1920,8 +1939,9 @@ void LineEdit::_shape() { } TS->shaped_text_set_preserve_control(text_rid, draw_control_chars); - const Ref<Font> &font = get_theme_font("font"); - int font_size = get_theme_font_size("font_size"); + 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 != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); TS->shaped_text_set_bidi_override(text_rid, structured_text_parser(st_parser, st_args, t)); @@ -1937,12 +1957,12 @@ void LineEdit::_shape() { void LineEdit::_fit_to_width() { if (align == ALIGN_FILL) { - Ref<StyleBox> style = get_theme_stylebox("normal"); + Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); int t_width = get_size().width - style->get_margin(SIDE_RIGHT) - style->get_margin(SIDE_LEFT); 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("clear") : right_icon; + Ref<Texture2D> r_icon = display_clear_icon ? Control::get_theme_icon(SNAME("clear")) : right_icon; t_width -= r_icon->get_width(); } TS->shaped_text_fit_to_width(text_rid, MAX(t_width, full_width)); @@ -2003,35 +2023,6 @@ int LineEdit::_get_menu_action_accelerator(const String &p_action) { } } -void LineEdit::_generate_context_menu() { - // Reorganize context menu. - menu->clear(); - if (editable) { - menu->add_item(RTR("Cut"), MENU_CUT, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_cut") : 0); - } - menu->add_item(RTR("Copy"), MENU_COPY, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_copy") : 0); - if (editable) { - menu->add_item(RTR("Paste"), MENU_PASTE, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_paste") : 0); - } - menu->add_separator(); - if (is_selecting_enabled()) { - menu->add_item(RTR("Select All"), MENU_SELECT_ALL, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_text_select_all") : 0); - } - if (editable) { - menu->add_item(RTR("Clear"), MENU_CLEAR); - menu->add_separator(); - menu->add_item(RTR("Undo"), MENU_UNDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_undo") : 0); - menu->add_item(RTR("Redo"), MENU_REDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_redo") : 0); - } - menu->add_separator(); - menu->add_submenu_item(RTR("Text writing direction"), "DirMenu"); - menu->add_separator(); - menu->add_check_item(RTR("Display control characters"), MENU_DISPLAY_UCC); - if (editable) { - menu->add_submenu_item(RTR("Insert control character"), "CTLMenu"); - } -} - bool LineEdit::_set(const StringName &p_name, const Variant &p_value) { String str = p_name; if (str.begins_with("opentype_features/")) { @@ -2094,7 +2085,6 @@ void LineEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_align", "align"), &LineEdit::set_align); ClassDB::bind_method(D_METHOD("get_align"), &LineEdit::get_align); - ClassDB::bind_method(D_METHOD("_gui_input"), &LineEdit::_gui_input); ClassDB::bind_method(D_METHOD("clear"), &LineEdit::clear); ClassDB::bind_method(D_METHOD("select", "from", "to"), &LineEdit::select, DEFVAL(0), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("select_all"), &LineEdit::select_all); @@ -2144,6 +2134,7 @@ void LineEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_secret_character"), &LineEdit::get_secret_character); ClassDB::bind_method(D_METHOD("menu_option", "option"), &LineEdit::menu_option); ClassDB::bind_method(D_METHOD("get_menu"), &LineEdit::get_menu); + ClassDB::bind_method(D_METHOD("is_menu_visible"), &LineEdit::is_menu_visible); ClassDB::bind_method(D_METHOD("set_context_menu_enabled", "enable"), &LineEdit::set_context_menu_enabled); ClassDB::bind_method(D_METHOD("is_context_menu_enabled"), &LineEdit::is_context_menu_enabled); ClassDB::bind_method(D_METHOD("set_virtual_keyboard_enabled", "enable"), &LineEdit::set_virtual_keyboard_enabled); @@ -2158,7 +2149,7 @@ void LineEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_right_icon"), &LineEdit::get_right_icon); ADD_SIGNAL(MethodInfo("text_changed", PropertyInfo(Variant::STRING, "new_text"))); - ADD_SIGNAL(MethodInfo("text_change_rejected")); + ADD_SIGNAL(MethodInfo("text_change_rejected", PropertyInfo(Variant::STRING, "rejected_substring"))); ADD_SIGNAL(MethodInfo("text_submitted", PropertyInfo(Variant::STRING, "new_text"))); BIND_ENUM_CONSTANT(ALIGN_LEFT); @@ -2198,7 +2189,7 @@ void LineEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "text"), "set_text", "get_text"); ADD_PROPERTY(PropertyInfo(Variant::INT, "align", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_align", "get_align"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "max_length"), "set_max_length", "get_max_length"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_length", PROPERTY_HINT_RANGE, "0,1000,1,or_greater"), "set_max_length", "get_max_length"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "secret"), "set_secret", "is_secret"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "secret_character"), "set_secret_character", "get_secret_character"); @@ -2221,11 +2212,89 @@ void LineEdit::_bind_methods() { 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"), "set_caret_column", "get_caret_column"); + 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"); } +void LineEdit::_ensure_menu() { + if (!menu) { + menu = memnew(PopupMenu); + add_child(menu, false, INTERNAL_MODE_FRONT); + + menu_dir = memnew(PopupMenu); + menu_dir->set_name("DirMenu"); + menu_dir->add_radio_check_item(RTR("Same as layout direction"), MENU_DIR_INHERITED); + menu_dir->add_radio_check_item(RTR("Auto-detect direction"), MENU_DIR_AUTO); + menu_dir->add_radio_check_item(RTR("Left-to-right"), MENU_DIR_LTR); + menu_dir->add_radio_check_item(RTR("Right-to-left"), MENU_DIR_RTL); + menu->add_child(menu_dir); + + menu_ctl = memnew(PopupMenu); + menu_ctl->set_name("CTLMenu"); + menu_ctl->add_item(RTR("Left-to-right mark (LRM)"), MENU_INSERT_LRM); + menu_ctl->add_item(RTR("Right-to-left mark (RLM)"), MENU_INSERT_RLM); + menu_ctl->add_item(RTR("Start of left-to-right embedding (LRE)"), MENU_INSERT_LRE); + menu_ctl->add_item(RTR("Start of right-to-left embedding (RLE)"), MENU_INSERT_RLE); + menu_ctl->add_item(RTR("Start of left-to-right override (LRO)"), MENU_INSERT_LRO); + menu_ctl->add_item(RTR("Start of right-to-left override (RLO)"), MENU_INSERT_RLO); + menu_ctl->add_item(RTR("Pop direction formatting (PDF)"), MENU_INSERT_PDF); + menu_ctl->add_separator(); + menu_ctl->add_item(RTR("Arabic letter mark (ALM)"), MENU_INSERT_ALM); + menu_ctl->add_item(RTR("Left-to-right isolate (LRI)"), MENU_INSERT_LRI); + menu_ctl->add_item(RTR("Right-to-left isolate (RLI)"), MENU_INSERT_RLI); + menu_ctl->add_item(RTR("First strong isolate (FSI)"), MENU_INSERT_FSI); + menu_ctl->add_item(RTR("Pop direction isolate (PDI)"), MENU_INSERT_PDI); + menu_ctl->add_separator(); + menu_ctl->add_item(RTR("Zero width joiner (ZWJ)"), MENU_INSERT_ZWJ); + menu_ctl->add_item(RTR("Zero width non-joiner (ZWNJ)"), MENU_INSERT_ZWNJ); + menu_ctl->add_item(RTR("Word joiner (WJ)"), MENU_INSERT_WJ); + menu_ctl->add_item(RTR("Soft hyphen (SHY)"), MENU_INSERT_SHY); + menu->add_child(menu_ctl); + + menu->connect("id_pressed", callable_mp(this, &LineEdit::menu_option)); + menu_dir->connect("id_pressed", callable_mp(this, &LineEdit::menu_option)); + menu_ctl->connect("id_pressed", callable_mp(this, &LineEdit::menu_option)); + } + + // Reorganize context menu. + menu->clear(); + if (editable) { + menu->add_item(RTR("Cut"), MENU_CUT, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_cut") : 0); + } + menu->add_item(RTR("Copy"), MENU_COPY, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_copy") : 0); + if (editable) { + menu->add_item(RTR("Paste"), MENU_PASTE, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_paste") : 0); + } + menu->add_separator(); + if (is_selecting_enabled()) { + menu->add_item(RTR("Select All"), MENU_SELECT_ALL, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_text_select_all") : 0); + } + if (editable) { + menu->add_item(RTR("Clear"), MENU_CLEAR); + menu->add_separator(); + menu->add_item(RTR("Undo"), MENU_UNDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_undo") : 0); + menu->add_item(RTR("Redo"), MENU_REDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_redo") : 0); + } + menu->add_separator(); + menu->add_submenu_item(RTR("Text writing direction"), "DirMenu"); + menu->add_separator(); + menu->add_check_item(RTR("Display control characters"), MENU_DISPLAY_UCC); + menu->set_item_checked(menu->get_item_index(MENU_DISPLAY_UCC), draw_control_chars); + if (editable) { + menu->add_submenu_item(RTR("Insert control character"), "CTLMenu"); + } + menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_INHERITED), text_direction == TEXT_DIRECTION_INHERITED); + menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_AUTO), text_direction == TEXT_DIRECTION_AUTO); + menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_LTR), text_direction == TEXT_DIRECTION_LTR); + menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_RTL), text_direction == TEXT_DIRECTION_RTL); + + if (editable) { + menu->set_item_disabled(menu->get_item_index(MENU_UNDO), !has_undo()); + menu->set_item_disabled(menu->get_item_index(MENU_REDO), !has_redo()); + } +} + LineEdit::LineEdit() { text_rid = TS->create_shaped_text(); _create_undo_state(); @@ -2236,49 +2305,12 @@ LineEdit::LineEdit() { set_mouse_filter(MOUSE_FILTER_STOP); caret_blink_timer = memnew(Timer); - add_child(caret_blink_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); - menu = memnew(PopupMenu); - add_child(menu); - - menu_dir = memnew(PopupMenu); - menu_dir->set_name("DirMenu"); - menu_dir->add_radio_check_item(RTR("Same as layout direction"), MENU_DIR_INHERITED); - menu_dir->add_radio_check_item(RTR("Auto-detect direction"), MENU_DIR_AUTO); - menu_dir->add_radio_check_item(RTR("Left-to-right"), MENU_DIR_LTR); - menu_dir->add_radio_check_item(RTR("Right-to-left"), MENU_DIR_RTL); - menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_INHERITED), true); - menu->add_child(menu_dir); - - menu_ctl = memnew(PopupMenu); - menu_ctl->set_name("CTLMenu"); - menu_ctl->add_item(RTR("Left-to-right mark (LRM)"), MENU_INSERT_LRM); - menu_ctl->add_item(RTR("Right-to-left mark (RLM)"), MENU_INSERT_RLM); - menu_ctl->add_item(RTR("Start of left-to-right embedding (LRE)"), MENU_INSERT_LRE); - menu_ctl->add_item(RTR("Start of right-to-left embedding (RLE)"), MENU_INSERT_RLE); - menu_ctl->add_item(RTR("Start of left-to-right override (LRO)"), MENU_INSERT_LRO); - menu_ctl->add_item(RTR("Start of right-to-left override (RLO)"), MENU_INSERT_RLO); - menu_ctl->add_item(RTR("Pop direction formatting (PDF)"), MENU_INSERT_PDF); - menu_ctl->add_separator(); - menu_ctl->add_item(RTR("Arabic letter mark (ALM)"), MENU_INSERT_ALM); - menu_ctl->add_item(RTR("Left-to-right isolate (LRI)"), MENU_INSERT_LRI); - menu_ctl->add_item(RTR("Right-to-left isolate (RLI)"), MENU_INSERT_RLI); - menu_ctl->add_item(RTR("First strong isolate (FSI)"), MENU_INSERT_FSI); - menu_ctl->add_item(RTR("Pop direction isolate (PDI)"), MENU_INSERT_PDI); - menu_ctl->add_separator(); - menu_ctl->add_item(RTR("Zero width joiner (ZWJ)"), MENU_INSERT_ZWJ); - menu_ctl->add_item(RTR("Zero width non-joiner (ZWNJ)"), MENU_INSERT_ZWNJ); - menu_ctl->add_item(RTR("Word joiner (WJ)"), MENU_INSERT_WJ); - menu_ctl->add_item(RTR("Soft hyphen (SHY)"), MENU_INSERT_SHY); - menu->add_child(menu_ctl); - set_editable(true); // Initialise to opposite first, so we get past the early-out in set_editable. - menu->connect("id_pressed", callable_mp(this, &LineEdit::menu_option)); - menu_dir->connect("id_pressed", callable_mp(this, &LineEdit::menu_option)); - menu_ctl->connect("id_pressed", callable_mp(this, &LineEdit::menu_option)); } LineEdit::~LineEdit() { diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index 12fec2f98b..e364a79c83 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -165,7 +165,6 @@ private: void _create_undo_state(); int _get_menu_action_accelerator(const String &p_action); - void _generate_context_menu(); void _shape(); void _fit_to_width(); @@ -198,10 +197,12 @@ private: void _backspace(bool p_word = false, bool p_all_to_left = false); void _delete(bool p_word = false, bool p_all_to_right = false); + void _ensure_menu(); + protected: void _notification(int p_what); static void _bind_methods(); - void _gui_input(Ref<InputEvent> p_event); + 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; @@ -222,6 +223,7 @@ public: void set_context_menu_enabled(bool p_enable); bool is_context_menu_enabled(); PopupMenu *get_menu() const; + bool is_menu_visible() const; void select(int p_from = 0, int p_to = -1); void select_all(); @@ -283,6 +285,8 @@ public: void copy_text(); void cut_text(); void paste_text(); + bool has_undo() const; + bool has_redo() const; void undo(); void redo(); diff --git a/scene/gui/link_button.cpp b/scene/gui/link_button.cpp index ee0618a991..925e6f5b97 100644 --- a/scene/gui/link_button.cpp +++ b/scene/gui/link_button.cpp @@ -32,8 +32,8 @@ #include "core/string/translation.h" void LinkButton::_shape() { - Ref<Font> font = get_theme_font("font"); - int font_size = get_theme_font_size("font_size"); + Ref<Font> font = get_theme_font(SNAME("font")); + int font_size = get_theme_font_size(SNAME("font_size")); text_buf->clear(); if (text_direction == Control::TEXT_DIRECTION_INHERITED) { @@ -41,12 +41,16 @@ void LinkButton::_shape() { } else { 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, text)); - text_buf->add_string(text, font, font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); + 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 != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); } void LinkButton::set_text(const String &p_text) { + if (text == p_text) { + return; + } text = p_text; + xl_text = atr(text); _shape(); minimum_size_changed(); update(); @@ -141,7 +145,13 @@ Size2 LinkButton::get_minimum_size() const { void LinkButton::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_TRANSLATION_CHANGED: + case NOTIFICATION_TRANSLATION_CHANGED: { + xl_text = atr(text); + _shape(); + + minimum_size_changed(); + update(); + } break; case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { update(); } break; @@ -158,41 +168,41 @@ void LinkButton::_notification(int p_what) { switch (get_draw_mode()) { case DRAW_NORMAL: { - color = get_theme_color("font_color"); + color = get_theme_color(SNAME("font_color")); do_underline = underline_mode == UNDERLINE_MODE_ALWAYS; } break; case DRAW_HOVER_PRESSED: case DRAW_PRESSED: { - if (has_theme_color("font_pressed_color")) { - color = get_theme_color("font_pressed_color"); + if (has_theme_color(SNAME("font_pressed_color"))) { + color = get_theme_color(SNAME("font_pressed_color")); } else { - color = get_theme_color("font_color"); + color = get_theme_color(SNAME("font_color")); } do_underline = underline_mode != UNDERLINE_MODE_NEVER; } break; case DRAW_HOVER: { - color = get_theme_color("font_hover_color"); + color = get_theme_color(SNAME("font_hover_color")); do_underline = underline_mode != UNDERLINE_MODE_NEVER; } break; case DRAW_DISABLED: { - color = get_theme_color("font_disabled_color"); + color = get_theme_color(SNAME("font_disabled_color")); do_underline = underline_mode == UNDERLINE_MODE_ALWAYS; } break; } if (has_focus()) { - Ref<StyleBox> style = get_theme_stylebox("focus"); + Ref<StyleBox> style = get_theme_stylebox(SNAME("focus")); style->draw(ci, Rect2(Point2(), size)); } int width = text_buf->get_line_width(); - Color font_outline_color = get_theme_color("font_outline_color"); - int outline_size = get_theme_constant("outline_size"); + Color font_outline_color = get_theme_color(SNAME("font_outline_color")); + int outline_size = get_theme_constant(SNAME("outline_size")); if (is_layout_rtl()) { if (outline_size > 0 && font_outline_color.a > 0) { text_buf->draw_outline(get_canvas_item(), Vector2(size.width - width, 0), outline_size, font_outline_color); @@ -206,7 +216,7 @@ void LinkButton::_notification(int p_what) { } if (do_underline) { - int underline_spacing = get_theme_constant("underline_spacing") + text_buf->get_line_underline_position(); + int underline_spacing = get_theme_constant(SNAME("underline_spacing")) + text_buf->get_line_underline_position(); int y = text_buf->get_line_ascent() + underline_spacing; if (is_layout_rtl()) { diff --git a/scene/gui/link_button.h b/scene/gui/link_button.h index 7eaa9f88b6..231543c63c 100644 --- a/scene/gui/link_button.h +++ b/scene/gui/link_button.h @@ -47,6 +47,7 @@ public: private: String text; + String xl_text; Ref<TextLine> text_buf; UnderlineMode underline_mode = UNDERLINE_MODE_ALWAYS; diff --git a/scene/gui/margin_container.cpp b/scene/gui/margin_container.cpp index 0e9610d0a3..50b4d192a9 100644 --- a/scene/gui/margin_container.cpp +++ b/scene/gui/margin_container.cpp @@ -31,10 +31,10 @@ #include "margin_container.h" Size2 MarginContainer::get_minimum_size() const { - int margin_left = get_theme_constant("margin_left"); - int margin_top = get_theme_constant("margin_top"); - int margin_right = get_theme_constant("margin_right"); - int margin_bottom = get_theme_constant("margin_bottom"); + int margin_left = get_theme_constant(SNAME("margin_left")); + int margin_top = get_theme_constant(SNAME("margin_top")); + int margin_right = get_theme_constant(SNAME("margin_right")); + int margin_bottom = get_theme_constant(SNAME("margin_bottom")); Size2 max; @@ -68,10 +68,10 @@ Size2 MarginContainer::get_minimum_size() const { void MarginContainer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_SORT_CHILDREN: { - int margin_left = get_theme_constant("margin_left"); - int margin_top = get_theme_constant("margin_top"); - int margin_right = get_theme_constant("margin_right"); - int margin_bottom = get_theme_constant("margin_bottom"); + int margin_left = get_theme_constant(SNAME("margin_left")); + int margin_top = get_theme_constant(SNAME("margin_top")); + int margin_right = get_theme_constant(SNAME("margin_right")); + int margin_bottom = get_theme_constant(SNAME("margin_bottom")); Size2 s = get_size(); diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index 1e9baa77fc..737ba84617 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -33,7 +33,7 @@ #include "core/os/keyboard.h" #include "scene/main/window.h" -void MenuButton::_unhandled_key_input(Ref<InputEvent> p_event) { +void MenuButton::unhandled_key_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (!_is_focus_owner_in_shorcut_context()) { @@ -44,7 +44,7 @@ void MenuButton::_unhandled_key_input(Ref<InputEvent> p_event) { return; } - if (p_event->is_pressed() && !p_event->is_echo() && (Object::cast_to<InputEventKey>(p_event.ptr()) || Object::cast_to<InputEventJoypadButton>(p_event.ptr()) || Object::cast_to<InputEventAction>(*p_event))) { + if (p_event->is_pressed() && !p_event->is_echo() && (Object::cast_to<InputEventKey>(p_event.ptr()) || Object::cast_to<InputEventJoypadButton>(p_event.ptr()) || Object::cast_to<InputEventAction>(*p_event) || Object::cast_to<InputEventShortcut>(*p_event))) { if (!get_parent() || !is_visible_in_tree() || is_disabled()) { return; } @@ -55,7 +55,38 @@ void MenuButton::_unhandled_key_input(Ref<InputEvent> p_event) { } } +void MenuButton::_popup_visibility_changed(bool p_visible) { + set_pressed(p_visible); + + if (!p_visible) { + set_process_internal(false); + return; + } + + if (switch_on_hover) { + Window *window = Object::cast_to<Window>(get_viewport()); + if (window) { + mouse_pos_adjusted = window->get_position(); + + if (window->is_embedded()) { + Window *window_parent = Object::cast_to<Window>(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()); + } + } + + set_process_internal(true); + } + } +} + void MenuButton::pressed() { + emit_signal(SNAME("about_to_popup")); Size2 size = get_size(); Point2 gp = get_screen_position(); @@ -69,8 +100,8 @@ void MenuButton::pressed() { popup->popup(); } -void MenuButton::_gui_input(Ref<InputEvent> p_event) { - BaseButton::_gui_input(p_event); +void MenuButton::gui_input(const Ref<InputEvent> &p_event) { + BaseButton::gui_input(p_event); } PopupMenu *MenuButton::get_popup() const { @@ -94,10 +125,22 @@ bool MenuButton::is_switch_on_hover() { } void MenuButton::_notification(int p_what) { - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - if (!is_visible_in_tree()) { - popup->hide(); - } + switch (p_what) { + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible_in_tree()) { + popup->hide(); + } + } break; + case NOTIFICATION_INTERNAL_PROCESS: { + Vector2i mouse_pos = DisplayServer::get_singleton()->mouse_get_position() - mouse_pos_adjusted; + MenuButton *menu_btn_other = Object::cast_to<MenuButton>(get_viewport()->gui_find_control(mouse_pos)); + + if (menu_btn_other && menu_btn_other != this && menu_btn_other->is_switch_on_hover() && !menu_btn_other->is_disabled() && + (get_parent()->is_ancestor_of(menu_btn_other) || menu_btn_other->get_parent()->is_ancestor_of(popup))) { + popup->hide(); + menu_btn_other->pressed(); + } + } break; } } @@ -129,9 +172,9 @@ MenuButton::MenuButton() { popup = memnew(PopupMenu); popup->hide(); - add_child(popup); - popup->connect("about_to_popup", callable_mp((BaseButton *)this, &BaseButton::set_pressed), varray(true)); // For when switching from another MenuButton. - popup->connect("popup_hide", callable_mp((BaseButton *)this, &BaseButton::set_pressed), varray(false)); + add_child(popup, false, INTERNAL_MODE_FRONT); + popup->connect("about_to_popup", callable_mp(this, &MenuButton::_popup_visibility_changed), varray(true)); + popup->connect("popup_hide", callable_mp(this, &MenuButton::_popup_visibility_changed), varray(false)); } MenuButton::~MenuButton() { diff --git a/scene/gui/menu_button.h b/scene/gui/menu_button.h index fd9ae6021e..730495b65d 100644 --- a/scene/gui/menu_button.h +++ b/scene/gui/menu_button.h @@ -42,15 +42,19 @@ class MenuButton : public Button { bool disable_shortcuts = false; PopupMenu *popup; + Vector2i mouse_pos_adjusted; + Array _get_items() const; void _set_items(const Array &p_items); - void _gui_input(Ref<InputEvent> p_event) override; + virtual void gui_input(const Ref<InputEvent> &p_event) override; + + void _popup_visibility_changed(bool p_visible); protected: void _notification(int p_what); static void _bind_methods(); - virtual void _unhandled_key_input(Ref<InputEvent> p_event) override; + virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; public: virtual void pressed() override; diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index e52b6917be..d16e96dbec 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -35,12 +35,12 @@ Size2 OptionButton::get_minimum_size() const { Size2 minsize = Button::get_minimum_size(); - if (has_theme_icon("arrow")) { - const Size2 padding = get_theme_stylebox("normal")->get_minimum_size(); - const Size2 arrow_size = Control::get_theme_icon("arrow")->get_size(); + if (has_theme_icon(SNAME("arrow"))) { + const Size2 padding = get_theme_stylebox(SNAME("normal"))->get_minimum_size(); + 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("hseparation"); + content_size.width += arrow_size.width + get_theme_constant(SNAME("hseparation")); content_size.height = MAX(content_size.height, arrow_size.height); minsize = content_size + padding; @@ -52,26 +52,26 @@ Size2 OptionButton::get_minimum_size() const { void OptionButton::_notification(int p_what) { switch (p_what) { case NOTIFICATION_DRAW: { - if (!has_theme_icon("arrow")) { + if (!has_theme_icon(SNAME("arrow"))) { return; } RID ci = get_canvas_item(); - Ref<Texture2D> arrow = Control::get_theme_icon("arrow"); + Ref<Texture2D> arrow = Control::get_theme_icon(SNAME("arrow")); Color clr = Color(1, 1, 1); - if (get_theme_constant("modulate_arrow")) { + if (get_theme_constant(SNAME("modulate_arrow"))) { switch (get_draw_mode()) { case DRAW_PRESSED: - clr = get_theme_color("font_pressed_color"); + clr = get_theme_color(SNAME("font_pressed_color")); break; case DRAW_HOVER: - clr = get_theme_color("font_hover_color"); + clr = get_theme_color(SNAME("font_hover_color")); break; case DRAW_DISABLED: - clr = get_theme_color("font_disabled_color"); + clr = get_theme_color(SNAME("font_disabled_color")); break; default: - clr = get_theme_color("font_color"); + clr = get_theme_color(SNAME("font_color")); } } @@ -79,22 +79,22 @@ void OptionButton::_notification(int p_what) { Point2 ofs; if (is_layout_rtl()) { - ofs = Point2(get_theme_constant("arrow_margin"), int(Math::abs((size.height - arrow->get_height()) / 2))); + ofs = Point2(get_theme_constant(SNAME("arrow_margin")), int(Math::abs((size.height - arrow->get_height()) / 2))); } else { - ofs = Point2(size.width - arrow->get_width() - get_theme_constant("arrow_margin"), int(Math::abs((size.height - arrow->get_height()) / 2))); + ofs = Point2(size.width - arrow->get_width() - get_theme_constant(SNAME("arrow_margin")), int(Math::abs((size.height - arrow->get_height()) / 2))); } arrow->draw(ci, ofs, clr); } break; case NOTIFICATION_TRANSLATION_CHANGED: case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_THEME_CHANGED: { - if (has_theme_icon("arrow")) { + if (has_theme_icon(SNAME("arrow"))) { if (is_layout_rtl()) { - _set_internal_margin(SIDE_LEFT, Control::get_theme_icon("arrow")->get_width()); + _set_internal_margin(SIDE_LEFT, Control::get_theme_icon(SNAME("arrow"))->get_width()); _set_internal_margin(SIDE_RIGHT, 0.f); } else { _set_internal_margin(SIDE_LEFT, 0.f); - _set_internal_margin(SIDE_RIGHT, Control::get_theme_icon("arrow")->get_width()); + _set_internal_margin(SIDE_RIGHT, Control::get_theme_icon(SNAME("arrow"))->get_width()); } } } break; @@ -107,7 +107,7 @@ void OptionButton::_notification(int p_what) { } void OptionButton::_focused(int p_which) { - emit_signal("item_focused", p_which); + emit_signal(SNAME("item_focused"), p_which); } void OptionButton::_selected(int p_which) { @@ -220,7 +220,7 @@ void OptionButton::_select(int p_which, bool p_emit) { set_icon(popup->get_item_icon(current)); if (is_inside_tree() && p_emit) { - emit_signal("item_selected", current); + emit_signal(SNAME("item_selected"), current); } } @@ -339,19 +339,19 @@ OptionButton::OptionButton() { set_toggle_mode(true); set_text_align(ALIGN_LEFT); if (is_layout_rtl()) { - if (has_theme_icon("arrow")) { - _set_internal_margin(SIDE_LEFT, Control::get_theme_icon("arrow")->get_width()); + if (has_theme_icon(SNAME("arrow"))) { + _set_internal_margin(SIDE_LEFT, Control::get_theme_icon(SNAME("arrow"))->get_width()); } } else { - if (has_theme_icon("arrow")) { - _set_internal_margin(SIDE_RIGHT, Control::get_theme_icon("arrow")->get_width()); + if (has_theme_icon(SNAME("arrow"))) { + _set_internal_margin(SIDE_RIGHT, Control::get_theme_icon(SNAME("arrow"))->get_width()); } } set_action_mode(ACTION_MODE_BUTTON_PRESS); popup = memnew(PopupMenu); popup->hide(); - add_child(popup); + add_child(popup, false, INTERNAL_MODE_FRONT); popup->connect("index_pressed", callable_mp(this, &OptionButton::_selected)); popup->connect("id_focused", callable_mp(this, &OptionButton::_focused)); popup->connect("popup_hide", callable_mp((BaseButton *)this, &BaseButton::set_pressed), varray(false)); diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h index d846e395ad..953337ecce 100644 --- a/scene/gui/option_button.h +++ b/scene/gui/option_button.h @@ -56,6 +56,10 @@ protected: static void _bind_methods(); public: + // ATTENTION: This is used by the POT generator's scene parser. If the number of properties returned by `_get_items()` ever changes, + // this value should be updated to reflect the new size. + static const int ITEM_PROPERTY_SIZE = 5; + void add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1); void add_item(const String &p_label, int p_id = -1); diff --git a/scene/gui/panel.cpp b/scene/gui/panel.cpp index 995e985c3a..e8e7e3d997 100644 --- a/scene/gui/panel.cpp +++ b/scene/gui/panel.cpp @@ -35,7 +35,7 @@ void Panel::_notification(int p_what) { if (p_what == NOTIFICATION_DRAW) { RID ci = get_canvas_item(); - Ref<StyleBox> style = mode == MODE_BACKGROUND ? get_theme_stylebox("panel") : get_theme_stylebox("panel_fg"); + Ref<StyleBox> style = mode == MODE_BACKGROUND ? get_theme_stylebox(SNAME("panel")) : get_theme_stylebox(SNAME("panel_fg")); style->draw(ci, Rect2(Point2(), get_size())); } } diff --git a/scene/gui/panel_container.cpp b/scene/gui/panel_container.cpp index 11d822c5e1..d910e1e882 100644 --- a/scene/gui/panel_container.cpp +++ b/scene/gui/panel_container.cpp @@ -33,10 +33,10 @@ Size2 PanelContainer::get_minimum_size() const { Ref<StyleBox> style; - if (has_theme_stylebox("panel")) { - style = get_theme_stylebox("panel"); + if (has_theme_stylebox(SNAME("panel"))) { + style = get_theme_stylebox(SNAME("panel")); } else { - style = get_theme_stylebox("panel", "PanelContainer"); + style = get_theme_stylebox(SNAME("panel"), SNAME("PanelContainer")); } Size2 ms; @@ -65,10 +65,10 @@ void PanelContainer::_notification(int p_what) { RID ci = get_canvas_item(); Ref<StyleBox> style; - if (has_theme_stylebox("panel")) { - style = get_theme_stylebox("panel"); + if (has_theme_stylebox(SNAME("panel"))) { + style = get_theme_stylebox(SNAME("panel")); } else { - style = get_theme_stylebox("panel", "PanelContainer"); + style = get_theme_stylebox(SNAME("panel"), SNAME("PanelContainer")); } style->draw(ci, Rect2(Point2(), get_size())); @@ -77,10 +77,10 @@ void PanelContainer::_notification(int p_what) { if (p_what == NOTIFICATION_SORT_CHILDREN) { Ref<StyleBox> style; - if (has_theme_stylebox("panel")) { - style = get_theme_stylebox("panel"); + if (has_theme_stylebox(SNAME("panel"))) { + style = get_theme_stylebox(SNAME("panel")); } else { - style = get_theme_stylebox("panel", "PanelContainer"); + style = get_theme_stylebox(SNAME("panel"), SNAME("PanelContainer")); } Size2 size = get_size(); diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index 36bcca61a7..e9414598a2 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -71,7 +71,8 @@ void Popup::_notification(int p_what) { _initialize_visible_parents(); } else { _deinitialize_visible_parents(); - emit_signal("popup_hide"); + emit_signal(SNAME("popup_hide")); + popped_up = false; } } break; @@ -103,9 +104,9 @@ void Popup::_close_pressed() { _deinitialize_visible_parents(); - call_deferred("hide"); + call_deferred(SNAME("hide")); - emit_signal("cancelled"); + emit_signal(SNAME("cancelled")); } void Popup::set_as_minsize() { @@ -254,5 +255,5 @@ void PopupPanel::_notification(int p_what) { PopupPanel::PopupPanel() { panel = memnew(Panel); - add_child(panel); + add_child(panel, false, INTERNAL_MODE_FRONT); } diff --git a/scene/gui/popup.h b/scene/gui/popup.h index 0355405d7c..c7090e7231 100644 --- a/scene/gui/popup.h +++ b/scene/gui/popup.h @@ -35,6 +35,8 @@ #include "core/templates/local_vector.h" +class Panel; + class Popup : public Window { GDCLASS(Popup, Window); diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 2100707d2d..c0a559e624 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -46,15 +46,15 @@ String PopupMenu::_get_accel_text(const Item &p_item) const { } Size2 PopupMenu::_get_contents_minimum_size() const { - int vseparation = get_theme_constant("vseparation"); - int hseparation = get_theme_constant("hseparation"); + int vseparation = get_theme_constant(SNAME("vseparation")); + int hseparation = get_theme_constant(SNAME("hseparation")); - Size2 minsize = get_theme_stylebox("panel")->get_minimum_size(); // Accounts for margin in the margin container + Size2 minsize = get_theme_stylebox(SNAME("panel"))->get_minimum_size(); // Accounts for margin in the margin container minsize.x += scroll_container->get_v_scrollbar()->get_size().width * 2; // Adds a buffer so that the scrollbar does not render over the top of content float max_w = 0.0; float icon_w = 0.0; - int check_w = MAX(get_theme_icon("checked")->get_width(), get_theme_icon("radio_checked")->get_width()) + hseparation; + int check_w = MAX(get_theme_icon(SNAME("checked"))->get_width(), get_theme_icon(SNAME("radio_checked"))->get_width()) + hseparation; int accel_max_w = 0; bool has_check = false; @@ -74,14 +74,14 @@ Size2 PopupMenu::_get_contents_minimum_size() const { size.width += items[i].text_buf->get_size().x; size.height += vseparation; - if (items[i].accel || (items[i].shortcut.is_valid() && items[i].shortcut->is_valid())) { + if (items[i].accel || (items[i].shortcut.is_valid() && items[i].shortcut->has_valid_event())) { int accel_w = hseparation * 2; accel_w += items[i].accel_text_buf->get_size().x; accel_max_w = MAX(accel_w, accel_max_w); } if (items[i].submenu != "") { - size.width += get_theme_icon("submenu")->get_width(); + size.width += get_theme_icon(SNAME("submenu"))->get_width(); } max_w = MAX(max_w, size.width); @@ -89,7 +89,7 @@ Size2 PopupMenu::_get_contents_minimum_size() const { minsize.height += size.height; } - int item_side_padding = get_theme_constant("item_start_padding") + get_theme_constant("item_end_padding"); + int item_side_padding = get_theme_constant(SNAME("item_start_padding")) + get_theme_constant(SNAME("item_end_padding")); minsize.width += max_w + icon_w + accel_max_w + item_side_padding; if (has_check) { @@ -112,24 +112,24 @@ int PopupMenu::_get_item_height(int p_item) const { int icon_height = items[p_item].get_icon_size().height; if (items[p_item].checkable_type) { - icon_height = MAX(icon_height, MAX(get_theme_icon("checked")->get_height(), get_theme_icon("radio_checked")->get_height())); + icon_height = MAX(icon_height, MAX(get_theme_icon(SNAME("checked"))->get_height(), get_theme_icon(SNAME("radio_checked"))->get_height())); } int text_height = items[p_item].text_buf->get_size().height; if (text_height == 0 && !items[p_item].separator) { - text_height = get_theme_font("font")->get_height(get_theme_font_size("font_size")); + text_height = get_theme_font(SNAME("font"))->get_height(get_theme_font_size(SNAME("font_size"))); } int separator_height = 0; if (items[p_item].separator) { - separator_height = MAX(get_theme_stylebox("separator")->get_minimum_size().height, MAX(get_theme_stylebox("labeled_separator_left")->get_minimum_size().height, get_theme_stylebox("labeled_separator_right")->get_minimum_size().height)); + separator_height = MAX(get_theme_stylebox(SNAME("separator"))->get_minimum_size().height, MAX(get_theme_stylebox(SNAME("labeled_separator_left"))->get_minimum_size().height, get_theme_stylebox(SNAME("labeled_separator_right"))->get_minimum_size().height)); } return MAX(separator_height, MAX(text_height, icon_height)); } int PopupMenu::_get_items_total_height() const { - int vsep = get_theme_constant("vseparation"); + int vsep = get_theme_constant(SNAME("vseparation")); // Get total height of all items by taking max of icon height and font height int items_total_height = 0; @@ -163,9 +163,9 @@ int PopupMenu::_get_mouse_over(const Point2 &p_over) const { return -1; } - Ref<StyleBox> style = get_theme_stylebox("panel"); // Accounts for margin in the margin container + Ref<StyleBox> style = get_theme_stylebox(SNAME("panel")); // Accounts for margin in the margin container - int vseparation = get_theme_constant("vseparation"); + int vseparation = get_theme_constant(SNAME("vseparation")); Point2 ofs = style->get_offset() + Point2(0, vseparation / 2); @@ -195,8 +195,8 @@ void PopupMenu::_activate_submenu(int p_over) { return; //already visible! } - Ref<StyleBox> style = get_theme_stylebox("panel"); - int vsep = get_theme_constant("vseparation"); + Ref<StyleBox> style = get_theme_stylebox(SNAME("panel")); + int vsep = get_theme_constant(SNAME("vseparation")); Point2 this_pos = get_position(); Rect2 this_rect(this_pos, get_size()); @@ -228,6 +228,7 @@ void PopupMenu::_activate_submenu(int p_over) { // Set autohide areas PopupMenu *submenu_pum = Object::cast_to<PopupMenu>(submenu_popup); if (submenu_pum) { + submenu_pum->take_mouse_focus(); // Make the position of the parent popup relative to submenu popup this_rect.position = this_rect.position - submenu_pum->get_position(); @@ -251,7 +252,7 @@ void PopupMenu::_submenu_timeout() { submenu_over = -1; } -void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) { +void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (p_event->is_action("ui_down") && p_event->is_pressed()) { @@ -264,7 +265,7 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) { for (int i = search_from; i < items.size(); i++) { if (!items[i].separator && !items[i].disabled) { mouse_over = i; - emit_signal("id_focused", i); + emit_signal(SNAME("id_focused"), i); _scroll_to_item(i); control->update(); set_input_as_handled(); @@ -278,7 +279,7 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) { for (int i = 0; i < search_from; i++) { if (!items[i].separator && !items[i].disabled) { mouse_over = i; - emit_signal("id_focused", i); + emit_signal(SNAME("id_focused"), i); _scroll_to_item(i); control->update(); set_input_as_handled(); @@ -296,7 +297,7 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) { for (int i = search_from; i >= 0; i--) { if (!items[i].separator && !items[i].disabled) { mouse_over = i; - emit_signal("id_focused", i); + emit_signal(SNAME("id_focused"), i); _scroll_to_item(i); control->update(); set_input_as_handled(); @@ -310,7 +311,7 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) { for (int i = items.size() - 1; i >= search_from; i--) { if (!items[i].separator && !items[i].disabled) { mouse_over = i; - emit_signal("id_focused", i); + emit_signal(SNAME("id_focused"), i); _scroll_to_item(i); control->update(); set_input_as_handled(); @@ -358,9 +359,10 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) { } int button_idx = b->get_button_index(); - if (b->is_pressed() || (!b->is_pressed() && during_grabbed_click)) { - // Allow activating item by releasing the LMB or any that was down when the popup appeared. - // However, if button was not held when opening menu, do not allow release to activate item. + if (!b->is_pressed()) { + // Activate the item on release of either the left mouse button or + // any mouse button held down when the popup was opened. + // This allows for opening the popup and triggering an action in a single mouse click. if (button_idx == MOUSE_BUTTON_LEFT || (initial_button_mask & (1 << (button_idx - 1)))) { bool was_during_grabbed_click = during_grabbed_click; during_grabbed_click = false; @@ -397,17 +399,17 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseMotion> m = p_event; if (m.is_valid()) { - if (!item_clickable_area.has_point(m->get_position())) { - return; - } - - for (List<Rect2>::Element *E = autohide_areas.front(); E; E = E->next()) { - if (!Rect2(Point2(), get_size()).has_point(m->get_position()) && E->get().has_point(m->get_position())) { + for (const Rect2 &E : autohide_areas) { + if (!Rect2(Point2(), get_size()).has_point(m->get_position()) && E.has_point(m->get_position())) { _close_pressed(); return; } } + if (!item_clickable_area.has_point(m->get_position())) { + return; + } + int over = _get_mouse_over(m->get_position()); int id = (over < 0 || items[over].separator || items[over].disabled) ? -1 : (items[over].id >= 0 ? items[over].id : over); @@ -459,7 +461,7 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) { if (items[i].text.findn(search_string) == 0) { mouse_over = i; - emit_signal("id_focused", i); + emit_signal(SNAME("id_focused"), i); _scroll_to_item(i); control->update(); set_input_as_handled(); @@ -474,37 +476,37 @@ void PopupMenu::_draw_items() { RID ci = control->get_canvas_item(); Size2 margin_size; - margin_size.width = margin_container->get_theme_constant("margin_right") + margin_container->get_theme_constant("margin_left"); - margin_size.height = margin_container->get_theme_constant("margin_top") + margin_container->get_theme_constant("margin_bottom"); + margin_size.width = margin_container->get_theme_constant(SNAME("margin_right")) + margin_container->get_theme_constant(SNAME("margin_left")); + margin_size.height = margin_container->get_theme_constant(SNAME("margin_top")) + margin_container->get_theme_constant(SNAME("margin_bottom")); // Space between the item content and the sides of popup menu. - int item_start_padding = get_theme_constant("item_start_padding"); - int item_end_padding = get_theme_constant("item_end_padding"); + int item_start_padding = get_theme_constant(SNAME("item_start_padding")); + int item_end_padding = get_theme_constant(SNAME("item_end_padding")); bool rtl = control->is_layout_rtl(); - Ref<StyleBox> style = get_theme_stylebox("panel"); - Ref<StyleBox> hover = get_theme_stylebox("hover"); + Ref<StyleBox> style = get_theme_stylebox(SNAME("panel")); + Ref<StyleBox> hover = get_theme_stylebox(SNAME("hover")); // In Item::checkable_type enum order (less the non-checkable member) - Ref<Texture2D> check[] = { get_theme_icon("checked"), get_theme_icon("radio_checked") }; - Ref<Texture2D> uncheck[] = { get_theme_icon("unchecked"), get_theme_icon("radio_unchecked") }; + Ref<Texture2D> check[] = { get_theme_icon(SNAME("checked")), get_theme_icon(SNAME("radio_checked")) }; + Ref<Texture2D> uncheck[] = { get_theme_icon(SNAME("unchecked")), get_theme_icon(SNAME("radio_unchecked")) }; Ref<Texture2D> submenu; if (rtl) { - submenu = get_theme_icon("submenu_mirrored"); + submenu = get_theme_icon(SNAME("submenu_mirrored")); } else { - submenu = get_theme_icon("submenu"); + submenu = get_theme_icon(SNAME("submenu")); } - Ref<StyleBox> separator = get_theme_stylebox("separator"); - Ref<StyleBox> labeled_separator_left = get_theme_stylebox("labeled_separator_left"); - Ref<StyleBox> labeled_separator_right = get_theme_stylebox("labeled_separator_right"); + Ref<StyleBox> separator = get_theme_stylebox(SNAME("separator")); + 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("vseparation"); - int hseparation = get_theme_constant("hseparation"); - Color font_color = get_theme_color("font_color"); - Color font_disabled_color = get_theme_color("font_disabled_color"); - Color font_accelerator_color = get_theme_color("font_accelerator_color"); - Color font_hover_color = get_theme_color("font_hover_color"); - Color font_separator_color = get_theme_color("font_separator_color"); + int vseparation = get_theme_constant(SNAME("vseparation")); + int hseparation = get_theme_constant(SNAME("hseparation")); + 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")); + Color font_hover_color = get_theme_color(SNAME("font_hover_color")); + Color font_separator_color = get_theme_color(SNAME("font_separator_color")); float scroll_width = scroll_container->get_v_scrollbar()->is_visible_in_tree() ? scroll_container->get_v_scrollbar()->get_size().width : 0; float display_width = control->get_size().width - scroll_width; @@ -525,7 +527,7 @@ void PopupMenu::_draw_items() { float check_ofs = 0.0; if (has_check) { - check_ofs = MAX(get_theme_icon("checked")->get_width(), get_theme_icon("radio_checked")->get_width()) + hseparation; + check_ofs = MAX(get_theme_icon(SNAME("checked"))->get_width(), get_theme_icon(SNAME("radio_checked"))->get_width()) + hseparation; } Point2 ofs = Point2(); @@ -606,8 +608,8 @@ void PopupMenu::_draw_items() { } // Text - Color font_outline_color = get_theme_color("font_outline_color"); - int outline_size = get_theme_constant("outline_size"); + Color font_outline_color = get_theme_color(SNAME("font_outline_color")); + int outline_size = get_theme_constant(SNAME("outline_size")); if (items[i].separator) { if (text != String()) { int center = (display_width - items[i].text_buf->get_size().width) / 2; @@ -635,7 +637,7 @@ void PopupMenu::_draw_items() { } // Accelerator / Shortcut - if (items[i].accel || (items[i].shortcut.is_valid() && items[i].shortcut->is_valid())) { + if (items[i].accel || (items[i].shortcut.is_valid() && items[i].shortcut->has_valid_event())) { if (rtl) { item_ofs.x = scroll_width + style->get_margin(SIDE_LEFT) + item_end_padding; } else { @@ -657,7 +659,7 @@ void PopupMenu::_draw_items() { } void PopupMenu::_draw_background() { - Ref<StyleBox> style = get_theme_stylebox("panel"); + Ref<StyleBox> style = get_theme_stylebox(SNAME("panel")); RID ci2 = margin_container->get_canvas_item(); style->draw(ci2, Rect2(Point2(), margin_container->get_size())); } @@ -691,8 +693,8 @@ void PopupMenu::_shape_item(int p_item) { if (items.write[p_item].dirty) { items.write[p_item].text_buf->clear(); - Ref<Font> font = get_theme_font("font"); - int font_size = get_theme_font_size("font_size"); + Ref<Font> font = get_theme_font(SNAME("font")); + int font_size = get_theme_font_size(SNAME("font_size")); if (items[p_item].text_direction == Control::TEXT_DIRECTION_INHERITED) { items.write[p_item].text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); @@ -722,7 +724,7 @@ void PopupMenu::_notification(int p_what) { case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_TRANSLATION_CHANGED: { for (int i = 0; i < items.size(); i++) { - items.write[i].xl_text = tr(items[i].text); + items.write[i].xl_text = atr(items[i].text); items.write[i].dirty = true; _shape_item(i); } @@ -747,12 +749,12 @@ void PopupMenu::_notification(int p_what) { } break; case NOTIFICATION_INTERNAL_PROCESS: { //only used when using operating system windows - if (get_window_id() != DisplayServer::INVALID_WINDOW_ID && autohide_areas.size()) { + if (!is_embedded() && autohide_areas.size()) { Point2 mouse_pos = DisplayServer::get_singleton()->mouse_get_position(); mouse_pos -= get_position(); - for (List<Rect2>::Element *E = autohide_areas.front(); E; E = E->next()) { - if (!Rect2(Point2(), get_size()).has_point(mouse_pos) && E->get().has_point(mouse_pos)) { + for (const Rect2 &E : autohide_areas) { + if (!Rect2(Point2(), get_size()).has_point(mouse_pos) && E.has_point(mouse_pos)) { _close_pressed(); return; } @@ -786,12 +788,12 @@ void PopupMenu::_notification(int p_what) { set_process_internal(false); } else { - if (get_window_id() != DisplayServer::INVALID_WINDOW_ID) { + if (!is_embedded()) { set_process_internal(true); } // Set margin on the margin container - Ref<StyleBox> panel_style = get_theme_stylebox("panel"); + 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)); @@ -807,7 +809,7 @@ void PopupMenu::_notification(int p_what) { #define ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel) \ item.text = p_label; \ - item.xl_text = tr(p_label); \ + item.xl_text = atr(p_label); \ item.id = p_id == -1 ? items.size() : p_id; \ item.accel = p_accel; @@ -887,7 +889,7 @@ void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int ERR_FAIL_COND_MSG(p_shortcut.is_null(), "Cannot add item with invalid Shortcut."); \ _ref_shortcut(p_shortcut); \ item.text = p_shortcut->get_name(); \ - item.xl_text = tr(item.text); \ + item.xl_text = atr(item.text); \ item.id = p_id == -1 ? items.size() : p_id; \ item.shortcut = p_shortcut; \ item.shortcut_is_global = p_global; @@ -956,7 +958,7 @@ void PopupMenu::add_icon_radio_check_shortcut(const Ref<Texture2D> &p_icon, cons void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, int p_id) { Item item; item.text = p_label; - item.xl_text = tr(p_label); + item.xl_text = atr(p_label); item.id = p_id == -1 ? items.size() : p_id; item.submenu = p_submenu; items.push_back(item); @@ -973,7 +975,7 @@ void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, void PopupMenu::set_item_text(int p_idx, const String &p_text) { ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].text = p_text; - items.write[p_idx].xl_text = tr(p_text); + items.write[p_idx].xl_text = atr(p_text); _shape_item(p_idx); control->update(); @@ -1274,13 +1276,13 @@ int PopupMenu::get_item_count() const { } bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only) { - uint32_t code = 0; + Key code = KEY_NONE; Ref<InputEventKey> k = p_event; if (k.is_valid()) { code = k->get_keycode(); - if (code == 0) { - code = k->get_unicode(); + if (code == KEY_NONE) { + code = (Key)k->get_unicode(); } if (k->is_ctrl_pressed()) { code |= KEY_MASK_CTRL; @@ -1301,7 +1303,7 @@ bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_fo continue; } - if (items[i].shortcut.is_valid() && items[i].shortcut->is_shortcut(p_event) && (items[i].shortcut_is_global || !p_for_global_only)) { + if (items[i].shortcut.is_valid() && items[i].shortcut->matches_event(p_event) && (items[i].shortcut_is_global || !p_for_global_only)) { activate_item(i); return true; } @@ -1376,8 +1378,8 @@ void PopupMenu::activate_item(int p_item) { need_hide = false; } - emit_signal("id_pressed", id); - emit_signal("index_pressed", p_item); + emit_signal(SNAME("id_pressed"), id); + emit_signal(SNAME("index_pressed"), p_item); if (need_hide) { hide(); @@ -1402,7 +1404,7 @@ void PopupMenu::add_separator(const String &p_text, int p_id) { sep.id = p_id; if (p_text != String()) { sep.text = p_text; - sep.xl_text = tr(p_text); + sep.xl_text = atr(p_text); } items.push_back(sep); control->update(); @@ -1580,8 +1582,6 @@ void PopupMenu::take_mouse_focus() { } void PopupMenu::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &PopupMenu::_gui_input); - ClassDB::bind_method(D_METHOD("add_item", "label", "id", "accel"), &PopupMenu::add_item, DEFVAL(-1), DEFVAL(0)); ClassDB::bind_method(D_METHOD("add_icon_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_item, DEFVAL(-1), DEFVAL(0)); ClassDB::bind_method(D_METHOD("add_check_item", "label", "id", "accel"), &PopupMenu::add_check_item, DEFVAL(-1), DEFVAL(0)); @@ -1690,7 +1690,7 @@ PopupMenu::PopupMenu() { // Margin Container margin_container = memnew(MarginContainer); margin_container->set_anchors_and_offsets_preset(Control::PRESET_WIDE); - add_child(margin_container); + add_child(margin_container, false, INTERNAL_MODE_FRONT); margin_container->connect("draw", callable_mp(this, &PopupMenu::_draw_background)); // Scroll Container @@ -1704,22 +1704,22 @@ PopupMenu::PopupMenu() { control->set_anchors_and_offsets_preset(Control::PRESET_WIDE); control->set_h_size_flags(Control::SIZE_EXPAND_FILL); control->set_v_size_flags(Control::SIZE_EXPAND_FILL); - scroll_container->add_child(control); + scroll_container->add_child(control, false, INTERNAL_MODE_FRONT); control->connect("draw", callable_mp(this, &PopupMenu::_draw_items)); - connect("window_input", callable_mp(this, &PopupMenu::_gui_input)); + connect("window_input", callable_mp(this, &PopupMenu::gui_input)); submenu_timer = memnew(Timer); submenu_timer->set_wait_time(0.3); submenu_timer->set_one_shot(true); submenu_timer->connect("timeout", callable_mp(this, &PopupMenu::_submenu_timeout)); - add_child(submenu_timer); + add_child(submenu_timer, false, INTERNAL_MODE_FRONT); minimum_lifetime_timer = memnew(Timer); minimum_lifetime_timer->set_wait_time(0.3); minimum_lifetime_timer->set_one_shot(true); minimum_lifetime_timer->connect("timeout", callable_mp(this, &PopupMenu::_minimum_lifetime_timeout)); - add_child(minimum_lifetime_timer); + add_child(minimum_lifetime_timer, false, INTERNAL_MODE_FRONT); } PopupMenu::~PopupMenu() { diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 74718395d3..428076c6da 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -31,10 +31,10 @@ #ifndef POPUP_MENU_H #define POPUP_MENU_H +#include "core/input/shortcut.h" #include "scene/gui/margin_container.h" #include "scene/gui/popup.h" #include "scene/gui/scroll_container.h" -#include "scene/gui/shortcut.h" #include "scene/resources/text_line.h" class PopupMenu : public Popup { @@ -107,7 +107,7 @@ class PopupMenu : public Popup { void _shape_item(int p_item); - void _gui_input(const Ref<InputEvent> &p_event); + virtual void gui_input(const Ref<InputEvent> &p_event); void _activate_submenu(int p_over); void _submenu_timeout(); @@ -140,11 +140,14 @@ class PopupMenu : public Popup { void _close_pressed(); protected: - friend class MenuButton; void _notification(int p_what); static void _bind_methods(); public: + // ATTENTION: This is used by the POT generator's scene parser. If the number of properties returned by `_get_items()` ever changes, + // this value should be updated to reflect the new size. + static const int ITEM_PROPERTY_SIZE = 10; + void add_item(const String &p_label, int p_id = -1, uint32_t p_accel = 0); void add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, uint32_t p_accel = 0); void add_check_item(const String &p_label, int p_id = -1, uint32_t p_accel = 0); diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp index 6e8dfd5994..2cfaaa2fde 100644 --- a/scene/gui/progress_bar.cpp +++ b/scene/gui/progress_bar.cpp @@ -32,10 +32,10 @@ #include "scene/resources/text_line.h" Size2 ProgressBar::get_minimum_size() const { - Ref<StyleBox> bg = get_theme_stylebox("bg"); - Ref<StyleBox> fg = get_theme_stylebox("fg"); - Ref<Font> font = get_theme_font("font"); - int font_size = get_theme_font_size("font_size"); + Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg")); + Ref<StyleBox> fg = get_theme_stylebox(SNAME("fg")); + Ref<Font> font = get_theme_font(SNAME("font")); + int font_size = get_theme_font_size(SNAME("font_size")); Size2 minimum_size = bg->get_minimum_size(); minimum_size.height = MAX(minimum_size.height, fg->get_minimum_size().height); @@ -53,11 +53,11 @@ Size2 ProgressBar::get_minimum_size() const { void ProgressBar::_notification(int p_what) { if (p_what == NOTIFICATION_DRAW) { - Ref<StyleBox> bg = get_theme_stylebox("bg"); - Ref<StyleBox> fg = get_theme_stylebox("fg"); - Ref<Font> font = get_theme_font("font"); - int font_size = get_theme_font_size("font_size"); - Color font_color = get_theme_color("font_color"); + Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg")); + Ref<StyleBox> fg = get_theme_stylebox(SNAME("fg")); + Ref<Font> font = get_theme_font(SNAME("font")); + int font_size = get_theme_font_size(SNAME("font_size")); + Color font_color = get_theme_color(SNAME("font_color")); draw_style_box(bg, Rect2(Point2(), get_size())); float r = get_as_ratio(); @@ -75,8 +75,8 @@ void ProgressBar::_notification(int p_what) { String txt = TS->format_number(itos(int(get_as_ratio() * 100))) + TS->percent_sign(); TextLine tl = TextLine(txt, font, font_size); Vector2 text_pos = (Point2(get_size().width - tl.get_size().x, get_size().height - tl.get_size().y) / 2).round(); - Color font_outline_color = get_theme_color("font_outline_color"); - int outline_size = get_theme_constant("outline_size"); + Color font_outline_color = get_theme_color(SNAME("font_outline_color")); + int outline_size = get_theme_constant(SNAME("outline_size")); if (outline_size > 0 && font_outline_color.a > 0) { tl.draw_outline(get_canvas_item(), text_pos, outline_size, font_outline_color); } diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp index 4ea1e1eb9f..92d4261d8d 100644 --- a/scene/gui/range.cpp +++ b/scene/gui/range.cpp @@ -42,7 +42,7 @@ TypedArray<String> Range::get_configuration_warnings() const { void Range::_value_changed_notify() { _value_changed(shared->val); - emit_signal("value_changed", shared->val); + emit_signal(SNAME("value_changed"), shared->val); update(); } @@ -57,7 +57,7 @@ void Range::Shared::emit_value_changed() { } void Range::_changed_notify(const char *p_what) { - emit_signal("changed"); + emit_signal(SNAME("changed")); update(); } diff --git a/scene/gui/rich_text_effect.cpp b/scene/gui/rich_text_effect.cpp index 39718a269a..236d106af8 100644 --- a/scene/gui/rich_text_effect.cpp +++ b/scene/gui/rich_text_effect.cpp @@ -32,8 +32,15 @@ #include "core/object/script_language.h" -void RichTextEffect::_bind_methods() { - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_process_custom_fx", PropertyInfo(Variant::OBJECT, "char_fx", PROPERTY_HINT_RESOURCE_TYPE, "CharFXTransform"))); +CharFXTransform::CharFXTransform() { +} + +CharFXTransform::~CharFXTransform() { + environment.clear(); +} + +void RichTextEffect::_bind_methods(){ + GDVIRTUAL_BIND(_process_custom_fx, "char_fx") } Variant RichTextEffect::get_bbcode() const { @@ -49,15 +56,10 @@ Variant RichTextEffect::get_bbcode() const { bool RichTextEffect::_process_effect_impl(Ref<CharFXTransform> p_cfx) { bool return_value = false; - if (get_script_instance()) { - Variant v = get_script_instance()->call("_process_custom_fx", p_cfx); - if (v.get_type() != Variant::BOOL) { - return_value = false; - } else { - return_value = (bool)v; - } + if (GDVIRTUAL_CALL(_process_custom_fx, p_cfx, return_value)) { + return return_value; } - return return_value; + return false; } RichTextEffect::RichTextEffect() { @@ -101,10 +103,3 @@ void CharFXTransform::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "glyph_index"), "set_glyph_index", "get_glyph_index"); ADD_PROPERTY(PropertyInfo(Variant::RID, "font"), "set_font", "get_font"); } - -CharFXTransform::CharFXTransform() { -} - -CharFXTransform::~CharFXTransform() { - environment.clear(); -} diff --git a/scene/gui/rich_text_effect.h b/scene/gui/rich_text_effect.h index d809fd502f..f5506542bb 100644 --- a/scene/gui/rich_text_effect.h +++ b/scene/gui/rich_text_effect.h @@ -32,20 +32,8 @@ #define RICH_TEXT_EFFECT_H #include "core/io/resource.h" - -class RichTextEffect : public Resource { - GDCLASS(RichTextEffect, Resource); - OBJ_SAVE_TYPE(RichTextEffect); - -protected: - static void _bind_methods(); - -public: - Variant get_bbcode() const; - bool _process_effect_impl(Ref<class CharFXTransform> p_cfx); - - RichTextEffect(); -}; +#include "core/object/gdvirtual.gen.inc" +#include "core/object/script_language.h" class CharFXTransform : public RefCounted { GDCLASS(CharFXTransform, RefCounted); @@ -59,9 +47,9 @@ public: bool outline = false; Point2 offset; Color color; - float elapsed_time = 0.0f; + double elapsed_time = 0.0f; Dictionary environment; - uint32_t glpyh_index = 0; + uint32_t glyph_index = 0; RID font; CharFXTransform(); @@ -69,8 +57,8 @@ public: Vector2i get_range() { return range; } void set_range(const Vector2i &p_range) { range = p_range; } - float get_elapsed_time() { return elapsed_time; } - void set_elapsed_time(float p_elapsed_time) { elapsed_time = p_elapsed_time; } + double get_elapsed_time() { return elapsed_time; } + void set_elapsed_time(double p_elapsed_time) { elapsed_time = p_elapsed_time; } bool is_visible() { return visibility; } void set_visibility(bool p_visibility) { visibility = p_visibility; } bool is_outline() { return outline; } @@ -80,8 +68,8 @@ public: Color get_color() { return color; } void set_color(Color p_color) { color = p_color; } - uint32_t get_glyph_index() const { return glpyh_index; }; - void set_glyph_index(uint32_t p_glpyh_index) { glpyh_index = p_glpyh_index; }; + uint32_t get_glyph_index() const { return glyph_index; }; + void set_glyph_index(uint32_t p_glyph_index) { glyph_index = p_glyph_index; }; RID get_font() const { return font; }; void set_font(RID p_font) { font = p_font; }; @@ -89,4 +77,20 @@ public: void set_environment(Dictionary p_environment) { environment = p_environment; } }; +class RichTextEffect : public Resource { + GDCLASS(RichTextEffect, Resource); + OBJ_SAVE_TYPE(RichTextEffect); + +protected: + static void _bind_methods(); + + GDVIRTUAL1RC(bool, _process_custom_fx, Ref<CharFXTransform>) + +public: + Variant get_bbcode() const; + bool _process_effect_impl(Ref<class CharFXTransform> p_cfx); + + RichTextEffect(); +}; + #endif // RICH_TEXT_EFFECT_H diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index f32ad2144a..562bac60c2 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -136,7 +136,7 @@ RichTextLabel::Item *RichTextLabel::_get_prev_item(Item *p_item, bool p_free) co } Rect2 RichTextLabel::_get_text_rect() { - Ref<StyleBox> style = get_theme_stylebox("normal"); + Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); return Rect2(style->get_offset(), get_size() - style->get_minimum_size()); } @@ -229,8 +229,8 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> switch (it->type) { case ITEM_TABLE: { ItemTable *table = static_cast<ItemTable *>(it); - int hseparation = get_theme_constant("table_hseparation"); - int vseparation = get_theme_constant("table_vseparation"); + int hseparation = get_theme_constant(SNAME("table_hseparation")); + int vseparation = get_theme_constant(SNAME("table_vseparation")); int col_count = table->columns.size(); for (int i = 0; i < col_count; i++) { @@ -238,9 +238,9 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> } int idx = 0; - for (List<Item *>::Element *E = table->subitems.front(); E; E = E->next()) { - ERR_CONTINUE(E->get()->type != ITEM_FRAME); // Children should all be frames. - ItemFrame *frame = static_cast<ItemFrame *>(E->get()); + 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++) { _resize_line(frame, i, p_base_font, p_base_font_size, 1); } @@ -316,9 +316,9 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> Vector2 offset; float row_height = 0.0; - for (List<Item *>::Element *E = table->subitems.front(); E; E = E->next()) { - ERR_CONTINUE(E->get()->type != ITEM_FRAME); // Children should all be frames. - ItemFrame *frame = static_cast<ItemFrame *>(E->get()); + for (Item *E : table->subitems) { + ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames. + ItemFrame *frame = static_cast<ItemFrame *>(E); int column = idx % col_count; @@ -366,7 +366,7 @@ 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; + l.offset.y = p_frame->lines[p_line - 1].offset.y + p_frame->lines[p_line - 1].text_buf->get_size().y + get_theme_constant(SNAME("line_separation")); } else { l.offset.y = 0; } @@ -458,8 +458,8 @@ 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("table_hseparation"); - int vseparation = get_theme_constant("table_vseparation"); + int hseparation = get_theme_constant(SNAME("table_hseparation")); + int vseparation = get_theme_constant(SNAME("table_vseparation")); int col_count = table->columns.size(); int t_char_count = 0; // Set minimums to zero. @@ -472,9 +472,9 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> const int available_width = p_width - hseparation * (col_count - 1); int idx = 0; - for (List<Item *>::Element *E = table->subitems.front(); E; E = E->next()) { - ERR_CONTINUE(E->get()->type != ITEM_FRAME); // Children should all be frames. - ItemFrame *frame = static_cast<ItemFrame *>(E->get()); + for (Item *E : table->subitems) { + ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames. + ItemFrame *frame = static_cast<ItemFrame *>(E); int column = idx % col_count; for (int i = 0; i < frame->lines.size(); i++) { @@ -556,7 +556,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> Vector2 offset; float row_height = 0.0; - for (List<Item *>::Element *E = table->subitems.front(); E; E = E->next()) { + for (const List<Item *>::Element *E = table->subitems.front(); E; E = E->next()) { ERR_CONTINUE(E->get()->type != ITEM_FRAME); // Children should all be frames. ItemFrame *frame = static_cast<ItemFrame *>(E->get()); @@ -614,7 +614,7 @@ 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; + l.offset.y = p_frame->lines[p_line - 1].offset.y + p_frame->lines[p_line - 1].text_buf->get_size().y + get_theme_constant(SNAME("line_separation")); } else { l.offset.y = 0; } @@ -672,11 +672,11 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o if (prefix != "") { Ref<Font> font = _find_font(l.from); if (font.is_null()) { - font = get_theme_font("normal_font"); + font = get_theme_font(SNAME("normal_font")); } int font_size = _find_font_size(l.from); if (font_size == -1) { - font_size = get_theme_font_size("normal_font_size"); + font_size = get_theme_font_size(SNAME("normal_font_size")); } if (rtl) { float offx = 0.0f; @@ -775,16 +775,16 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } break; case ITEM_TABLE: { ItemTable *table = static_cast<ItemTable *>(it); - Color odd_row_bg = get_theme_color("table_odd_row_bg"); - Color even_row_bg = get_theme_color("table_even_row_bg"); - Color border = get_theme_color("table_border"); - int hseparation = get_theme_constant("table_hseparation"); + 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 col_count = table->columns.size(); int row_count = table->rows.size(); int idx = 0; - for (List<Item *>::Element *E = table->subitems.front(); E; E = E->next()) { - ItemFrame *frame = static_cast<ItemFrame *>(E->get()); + for (Item *E : table->subitems) { + ItemFrame *frame = static_cast<ItemFrame *>(E); int col = idx % col_count; int row = idx / col_count; @@ -874,7 +874,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o charfx->visibility = visible; charfx->outline = true; charfx->font = frid; - charfx->glpyh_index = gl; + charfx->glyph_index = gl; charfx->offset = fx_offset; charfx->color = font_color; @@ -884,7 +884,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o fx_offset += charfx->offset; font_color = charfx->color; frid = charfx->font; - gl = charfx->glpyh_index; + gl = charfx->glyph_index; visible &= charfx->visibility; } } else if (item_fx->type == ITEM_SHAKE) { @@ -916,7 +916,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } } - Point2 shadow_ofs(get_theme_constant("shadow_offset_x"), get_theme_constant("shadow_offset_y")); + Point2 shadow_ofs(get_theme_constant(SNAME("shadow_offset_x")), get_theme_constant(SNAME("shadow_offset_y"))); // Draw glyph outlines. for (int j = 0; j < glyphs[i].repeat; j++) { @@ -940,8 +940,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o _draw_fbg_boxes(ci, rid, fbg_line_off, it_from, it_to, chr_range.x, chr_range.y, 0); // Draw main text. - Color selection_fg = get_theme_color("font_selected_color"); - Color selection_bg = get_theme_color("selection_color"); + Color selection_fg = get_theme_color(SNAME("font_selected_color")); + Color selection_bg = get_theme_color(SNAME("selection_color")); int sel_start = -1; int sel_end = -1; @@ -1026,7 +1026,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o charfx->visibility = visible; charfx->outline = false; charfx->font = frid; - charfx->glpyh_index = gl; + charfx->glyph_index = gl; charfx->offset = fx_offset; charfx->color = font_color; @@ -1036,7 +1036,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o fx_offset += charfx->offset; font_color = charfx->color; frid = charfx->font; - gl = charfx->glpyh_index; + gl = charfx->glyph_index; visible &= charfx->visibility; } } else if (item_fx->type == ITEM_SHAKE) { @@ -1127,7 +1127,7 @@ void RichTextLabel::_find_click(ItemFrame *p_frame, const Point2i &p_click, Item 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()) { _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; + ofs.y += main->lines[from_line].text_buf->get_size().y + get_theme_constant(SNAME("line_separation")); if (((r_click_item != nullptr) && ((*r_click_item) != nullptr)) || ((r_click_frame != nullptr) && ((*r_click_frame) != nullptr))) { if (r_outside != nullptr) { *r_outside = false; @@ -1192,8 +1192,8 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V if (rect.has_point(p_click - p_ofs - off)) { switch (it->type) { case ITEM_TABLE: { - int hseparation = get_theme_constant("table_hseparation"); - int vseparation = get_theme_constant("table_vseparation"); + int hseparation = get_theme_constant(SNAME("table_hseparation")); + int vseparation = get_theme_constant(SNAME("table_vseparation")); ItemTable *table = static_cast<ItemTable *>(it); @@ -1203,8 +1203,8 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V int col_count = table->columns.size(); int row_count = table->rows.size(); - for (List<Item *>::Element *E = table->subitems.front(); E; E = E->next()) { - ItemFrame *frame = static_cast<ItemFrame *>(E->get()); + for (Item *E : table->subitems) { + ItemFrame *frame = static_cast<ItemFrame *>(E); int col = idx % col_count; int row = idx / col_count; @@ -1323,7 +1323,7 @@ void RichTextLabel::_update_scroll() { } } -void RichTextLabel::_update_fx(RichTextLabel::ItemFrame *p_frame, float p_delta_time) { +void RichTextLabel::_update_fx(RichTextLabel::ItemFrame *p_frame, double p_delta_time) { Item *it = p_frame; while (it) { ItemFX *ifx = nullptr; @@ -1362,7 +1362,7 @@ void RichTextLabel::_notification(int p_what) { case NOTIFICATION_MOUSE_EXIT: { if (meta_hovering) { meta_hovering = nullptr; - emit_signal("meta_hover_ended", current_meta); + emit_signal(SNAME("meta_hover_ended"), current_meta); current_meta = false; update(); } @@ -1395,11 +1395,11 @@ void RichTextLabel::_notification(int p_what) { Size2 size = get_size(); Rect2 text_rect = _get_text_rect(); - draw_style_box(get_theme_stylebox("normal"), Rect2(Point2(), size)); + draw_style_box(get_theme_stylebox(SNAME("normal")), Rect2(Point2(), size)); if (has_focus()) { RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, true); - draw_style_box(get_theme_stylebox("focus"), Rect2(Point2(), size)); + draw_style_box(get_theme_stylebox(SNAME("focus")), Rect2(Point2(), size)); RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false); } @@ -1419,13 +1419,13 @@ void RichTextLabel::_notification(int p_what) { if (from_line >= main->lines.size()) { break; //nothing to draw } - Ref<Font> base_font = get_theme_font("normal_font"); - Color base_color = get_theme_color("default_color"); - Color outline_color = get_theme_color("font_outline_color"); - int outline_size = get_theme_constant("outline_size"); - Color font_shadow_color = get_theme_color("font_shadow_color"); - bool use_outline = get_theme_constant("shadow_as_outline"); - Point2 shadow_ofs(get_theme_constant("shadow_offset_x"), get_theme_constant("shadow_offset_y")); + 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")); + int outline_size = get_theme_constant(SNAME("outline_size")); + Color font_shadow_color = get_theme_color(SNAME("font_shadow_color")); + bool use_outline = get_theme_constant(SNAME("shadow_as_outline")); + Point2 shadow_ofs(get_theme_constant(SNAME("shadow_offset_x")), get_theme_constant(SNAME("shadow_offset_y"))); visible_paragraph_count = 0; visible_line_count = 0; @@ -1435,13 +1435,13 @@ void RichTextLabel::_notification(int p_what) { while (ofs.y < size.height && from_line < main->lines.size()) { visible_paragraph_count++; visible_line_count += _draw_line(main, from_line, ofs, text_rect.size.x, base_color, outline_size, outline_color, font_shadow_color, use_outline, shadow_ofs); - ofs.y += main->lines[from_line].text_buf->get_size().y; + ofs.y += main->lines[from_line].text_buf->get_size().y + get_theme_constant(SNAME("line_separation")); from_line++; } } break; case NOTIFICATION_INTERNAL_PROCESS: { if (is_visible_in_tree()) { - float dt = get_process_delta_time(); + double dt = get_process_delta_time(); _update_fx(main, dt); update(); } @@ -1451,7 +1451,7 @@ void RichTextLabel::_notification(int p_what) { Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const { if (!underline_meta) { - return CURSOR_ARROW; + return get_default_cursor_shape(); } if (selection.click_item) { @@ -1459,11 +1459,11 @@ Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const } if (main->first_invalid_line < main->lines.size()) { - return CURSOR_ARROW; //invalid + return get_default_cursor_shape(); //invalid } if (main->first_resized_line < main->lines.size()) { - return CURSOR_ARROW; //invalid + return get_default_cursor_shape(); //invalid } Item *item = nullptr; @@ -1474,10 +1474,10 @@ Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const return CURSOR_POINTING_HAND; } - return CURSOR_ARROW; + return get_default_cursor_shape(); } -void RichTextLabel::_gui_input(Ref<InputEvent> p_event) { +void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventMouseButton> b = p_event; @@ -1568,7 +1568,7 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) { Variant meta; if (!outside && _find_meta(c_item, &meta)) { //meta clicked - emit_signal("meta_clicked", meta); + emit_signal(SNAME("meta_clicked"), meta); } } } @@ -1611,11 +1611,11 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) { handled = true; } if (k->is_action("ui_up") && vscroll->is_visible_in_tree()) { - vscroll->set_value(vscroll->get_value() - get_theme_font("normal_font")->get_height(get_theme_font_size("normal_font_size"))); + vscroll->set_value(vscroll->get_value() - get_theme_font(SNAME("normal_font"))->get_height(get_theme_font_size(SNAME("normal_font_size")))); handled = true; } if (k->is_action("ui_down") && vscroll->is_visible_in_tree()) { - vscroll->set_value(vscroll->get_value() + get_theme_font("normal_font")->get_height(get_theme_font_size("normal_font_size"))); + vscroll->set_value(vscroll->get_value() + get_theme_font(SNAME("normal_font"))->get_height(get_theme_font_size(SNAME("normal_font_size")))); handled = true; } if (k->is_action("ui_home") && vscroll->is_visible_in_tree()) { @@ -1693,15 +1693,15 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) { if (c_item && !outside && _find_meta(c_item, &meta, &item_meta)) { if (meta_hovering != item_meta) { if (meta_hovering) { - emit_signal("meta_hover_ended", current_meta); + emit_signal(SNAME("meta_hover_ended"), current_meta); } meta_hovering = item_meta; current_meta = meta; - emit_signal("meta_hover_started", meta); + emit_signal(SNAME("meta_hover_started"), meta); } } else if (meta_hovering) { meta_hovering = nullptr; - emit_signal("meta_hover_ended", current_meta); + emit_signal(SNAME("meta_hover_ended"), current_meta); current_meta = false; } } @@ -2080,8 +2080,8 @@ bool RichTextLabel::_find_layout_subitem(Item *from, Item *to) { return true; } - for (List<Item *>::Element *E = from->subitems.front(); E; E = E->next()) { - bool layout = _find_layout_subitem(E->get(), to); + for (Item *E : from->subitems) { + bool layout = _find_layout_subitem(E, to); if (layout) { return true; @@ -2105,8 +2105,8 @@ void RichTextLabel::_validate_line_caches(ItemFrame *p_frame) { } Rect2 text_rect = _get_text_rect(); - Ref<Font> base_font = get_theme_font("normal_font"); - int base_font_size = get_theme_font_size("normal_font_size"); + Ref<Font> base_font = get_theme_font(SNAME("normal_font")); + int base_font_size = get_theme_font_size(SNAME("normal_font_size")); 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); @@ -2140,8 +2140,8 @@ void RichTextLabel::_validate_line_caches(ItemFrame *p_frame) { } Rect2 text_rect = _get_text_rect(); - Ref<Font> base_font = get_theme_font("normal_font"); - int base_font_size = get_theme_font_size("normal_font_size"); + Ref<Font> base_font = get_theme_font(SNAME("normal_font")); + int base_font_size = get_theme_font_size(SNAME("normal_font_size")); 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++) { @@ -2289,7 +2289,7 @@ 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, VAlign p_align) { +void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlign p_align) { if (current->type == ITEM_TABLE) { return; } @@ -2406,35 +2406,35 @@ void RichTextLabel::push_font(const Ref<Font> &p_font) { } void RichTextLabel::push_normal() { - Ref<Font> normal_font = get_theme_font("normal_font"); + Ref<Font> normal_font = get_theme_font(SNAME("normal_font")); ERR_FAIL_COND(normal_font.is_null()); push_font(normal_font); } void RichTextLabel::push_bold() { - Ref<Font> bold_font = get_theme_font("bold_font"); + Ref<Font> bold_font = get_theme_font(SNAME("bold_font")); ERR_FAIL_COND(bold_font.is_null()); push_font(bold_font); } void RichTextLabel::push_bold_italics() { - Ref<Font> bold_italics_font = get_theme_font("bold_italics_font"); + Ref<Font> bold_italics_font = get_theme_font(SNAME("bold_italics_font")); ERR_FAIL_COND(bold_italics_font.is_null()); push_font(bold_italics_font); } void RichTextLabel::push_italics() { - Ref<Font> italics_font = get_theme_font("italics_font"); + Ref<Font> italics_font = get_theme_font(SNAME("italics_font")); ERR_FAIL_COND(italics_font.is_null()); push_font(italics_font); } void RichTextLabel::push_mono() { - Ref<Font> mono_font = get_theme_font("mono_font"); + Ref<Font> mono_font = get_theme_font(SNAME("mono_font")); ERR_FAIL_COND(mono_font.is_null()); push_font(mono_font); @@ -2534,7 +2534,7 @@ void RichTextLabel::push_meta(const Variant &p_meta) { _add_item(item, true); } -void RichTextLabel::push_table(int p_columns, VAlign p_align) { +void RichTextLabel::push_table(int p_columns, InlineAlign p_align) { ERR_FAIL_COND(p_columns < 1); ItemTable *item = memnew(ItemTable); @@ -2774,13 +2774,13 @@ Error RichTextLabel::append_bbcode(const String &p_bbcode) { int pos = 0; List<String> tag_stack; - Ref<Font> normal_font = get_theme_font("normal_font"); - Ref<Font> bold_font = get_theme_font("bold_font"); - Ref<Font> italics_font = get_theme_font("italics_font"); - Ref<Font> bold_italics_font = get_theme_font("bold_italics_font"); - Ref<Font> mono_font = get_theme_font("mono_font"); + Ref<Font> normal_font = get_theme_font(SNAME("normal_font")); + Ref<Font> bold_font = get_theme_font(SNAME("bold_font")); + Ref<Font> italics_font = get_theme_font(SNAME("italics_font")); + Ref<Font> bold_italics_font = get_theme_font(SNAME("bold_italics_font")); + Ref<Font> mono_font = get_theme_font(SNAME("mono_font")); - Color base_color = get_theme_color("default_color"); + Color base_color = get_theme_color(SNAME("default_color")); int indent_level = 0; @@ -2897,18 +2897,35 @@ Error RichTextLabel::append_bbcode(const String &p_bbcode) { columns = 1; } - VAlign align = VALIGN_TOP; - if (subtag.size() > 1) { + int align = INLINE_ALIGN_TOP; + if (subtag.size() > 2) { + if (subtag[1] == "top" || subtag[1] == "t") { + align = INLINE_ALIGN_TOP_TO; + } else if (subtag[1] == "center" || subtag[1] == "c") { + align = INLINE_ALIGN_CENTER_TO; + } else if (subtag[1] == "bottom" || subtag[1] == "b") { + align = INLINE_ALIGN_BOTTOM_TO; + } + if (subtag[2] == "top" || subtag[2] == "t") { + align |= INLINE_ALIGN_TO_TOP; + } else if (subtag[2] == "center" || subtag[2] == "c") { + align |= INLINE_ALIGN_TO_CENTER; + } else if (subtag[2] == "baseline" || subtag[2] == "l") { + align |= INLINE_ALIGN_TO_BASELINE; + } else if (subtag[2] == "bottom" || subtag[2] == "b") { + align |= INLINE_ALIGN_TO_BOTTOM; + } + } else if (subtag.size() > 1) { if (subtag[1] == "top" || subtag[1] == "t") { - align = VALIGN_TOP; + align = INLINE_ALIGN_TOP; } else if (subtag[1] == "center" || subtag[1] == "c") { - align = VALIGN_CENTER; + align = INLINE_ALIGN_CENTER; } else if (subtag[1] == "bottom" || subtag[1] == "b") { - align = VALIGN_BOTTOM; + align = INLINE_ALIGN_BOTTOM; } } - push_table(columns, align); + push_table(columns, (InlineAlign)align); pos = brk_end + 1; tag_stack.push_front("table"); } else if (tag == "cell") { @@ -3140,11 +3157,11 @@ Error RichTextLabel::append_bbcode(const String &p_bbcode) { tag_stack.push_front("url"); } else if (tag.begins_with("dropcap")) { Vector<String> subtag = tag.substr(5, tag.length()).split(" "); - Ref<Font> f = get_theme_font("normal_font"); - int fs = get_theme_font_size("normal_font_size") * 3; - Color color = get_theme_color("default_color"); - Color outline_color = get_theme_color("outline_color"); - int outline_size = get_theme_constant("outline_size"); + Ref<Font> f = get_theme_font(SNAME("normal_font")); + int fs = get_theme_font_size(SNAME("normal_font_size")) * 3; + 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")); Rect2 dropcap_margins = Rect2(); for (int i = 0; i < subtag.size(); i++) { @@ -3187,15 +3204,34 @@ Error RichTextLabel::append_bbcode(const String &p_bbcode) { pos = end; tag_stack.push_front(bbcode_name); } else if (tag.begins_with("img")) { - VAlign align = VALIGN_TOP; + int align = INLINE_ALIGN_CENTER; if (tag.begins_with("img=")) { - String al = tag.substr(4, tag.length()); - if (al == "top" || al == "t") { - align = VALIGN_TOP; - } else if (al == "center" || al == "c") { - align = VALIGN_CENTER; - } else if (al == "bottom" || al == "b") { - align = VALIGN_BOTTOM; + Vector<String> subtag = tag.substr(4, tag.length()).split(","); + if (subtag.size() > 1) { + if (subtag[0] == "top" || subtag[0] == "t") { + align = INLINE_ALIGN_TOP_TO; + } else if (subtag[0] == "center" || subtag[0] == "c") { + align = INLINE_ALIGN_CENTER_TO; + } else if (subtag[0] == "bottom" || subtag[0] == "b") { + align = INLINE_ALIGN_BOTTOM_TO; + } + if (subtag[1] == "top" || subtag[1] == "t") { + align |= INLINE_ALIGN_TO_TOP; + } else if (subtag[1] == "center" || subtag[1] == "c") { + align |= INLINE_ALIGN_TO_CENTER; + } else if (subtag[1] == "baseline" || subtag[1] == "l") { + align |= INLINE_ALIGN_TO_BASELINE; + } else if (subtag[1] == "bottom" || subtag[1] == "b") { + align |= INLINE_ALIGN_TO_BOTTOM; + } + } else if (subtag.size() > 0) { + if (subtag[0] == "top" || subtag[0] == "t") { + align = INLINE_ALIGN_TOP; + } else if (subtag[0] == "center" || subtag[0] == "c") { + align = INLINE_ALIGN_CENTER; + } else if (subtag[0] == "bottom" || subtag[0] == "b") { + align = INLINE_ALIGN_BOTTOM; + } } } @@ -3236,7 +3272,7 @@ Error RichTextLabel::append_bbcode(const String &p_bbcode) { } } - add_image(texture, width, height, color, align); + add_image(texture, width, height, color, (InlineAlign)align); } pos = end; @@ -3448,8 +3484,8 @@ Error RichTextLabel::append_bbcode(const String &p_bbcode) { } Vector<ItemFX *> fx_items; - for (List<Item *>::Element *E = main->subitems.front(); E; E = E->next()) { - Item *subitem = static_cast<Item *>(E->get()); + for (Item *E : main->subitems) { + Item *subitem = static_cast<Item *>(E); _fetch_item_fx_stack(subitem, fx_items); if (fx_items.size()) { @@ -3719,9 +3755,9 @@ String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p ItemTable *table = static_cast<ItemTable *>(it); int idx = 0; int col_count = table->columns.size(); - for (List<Item *>::Element *E = table->subitems.front(); E; E = E->next()) { - ERR_CONTINUE(E->get()->type != ITEM_FRAME); // Children should all be frames. - ItemFrame *frame = static_cast<ItemFrame *>(E->get()); + for (Item *E : table->subitems) { + ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames. + ItemFrame *frame = static_cast<ItemFrame *>(E); int column = idx % col_count; for (int i = 0; i < frame->lines.size(); i++) { @@ -3910,24 +3946,15 @@ float RichTextLabel::get_percent_visible() const { return percent_visible; } -void RichTextLabel::set_effects(const Vector<Variant> &effects) { - custom_effects.clear(); - for (int i = 0; i < effects.size(); i++) { - Ref<RichTextEffect> effect = Ref<RichTextEffect>(effects[i]); - custom_effects.push_back(effect); - } - +void RichTextLabel::set_effects(Array p_effects) { + custom_effects = p_effects; if ((bbcode != "") && use_bbcode) { parse_bbcode(bbcode); } } -Vector<Variant> RichTextLabel::get_effects() { - Vector<Variant> r; - for (int i = 0; i < custom_effects.size(); i++) { - r.push_back(custom_effects[i]); - } - return r; +Array RichTextLabel::get_effects() { + return custom_effects; } void RichTextLabel::install_effect(const Variant effect) { @@ -3957,11 +3984,10 @@ void RichTextLabel::_validate_property(PropertyInfo &property) const { } void RichTextLabel::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &RichTextLabel::_gui_input); ClassDB::bind_method(D_METHOD("get_text"), &RichTextLabel::get_text); ClassDB::bind_method(D_METHOD("add_text", "text"), &RichTextLabel::add_text); ClassDB::bind_method(D_METHOD("set_text", "text"), &RichTextLabel::set_text); - ClassDB::bind_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align"), &RichTextLabel::add_image, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(VALIGN_TOP)); + ClassDB::bind_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align"), &RichTextLabel::add_image, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGN_CENTER)); ClassDB::bind_method(D_METHOD("newline"), &RichTextLabel::add_newline); ClassDB::bind_method(D_METHOD("remove_line", "line"), &RichTextLabel::remove_line); ClassDB::bind_method(D_METHOD("push_font", "font"), &RichTextLabel::push_font); @@ -3981,7 +4007,7 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("push_meta", "data"), &RichTextLabel::push_meta); ClassDB::bind_method(D_METHOD("push_underline"), &RichTextLabel::push_underline); ClassDB::bind_method(D_METHOD("push_strikethrough"), &RichTextLabel::push_strikethrough); - ClassDB::bind_method(D_METHOD("push_table", "columns", "inline_align"), &RichTextLabel::push_table, DEFVAL(VALIGN_TOP)); + ClassDB::bind_method(D_METHOD("push_table", "columns", "inline_align"), &RichTextLabel::push_table, DEFVAL(INLINE_ALIGN_TOP)); ClassDB::bind_method(D_METHOD("push_dropcap", "string", "font", "size", "dropcap_margins", "color", "outline_size", "outline_color"), &RichTextLabel::push_dropcap, DEFVAL(Rect2()), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(0, 0, 0, 0))); ClassDB::bind_method(D_METHOD("set_table_column_expand", "column", "expand", "ratio"), &RichTextLabel::set_table_column_expand); ClassDB::bind_method(D_METHOD("set_cell_row_background_color", "odd_row_bg", "even_row_bg"), &RichTextLabel::set_cell_row_background_color); @@ -4168,7 +4194,7 @@ void RichTextLabel::set_fixed_size_to_width(int p_width) { } Size2 RichTextLabel::get_minimum_size() const { - Ref<StyleBox> style = get_theme_stylebox("normal"); + Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); Size2 size = style->get_minimum_size(); if (fixed_width != -1) { @@ -4244,12 +4270,13 @@ void RichTextLabel::_draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item Ref<RichTextEffect> RichTextLabel::_get_custom_effect_by_code(String p_bbcode_identifier) { for (int i = 0; i < custom_effects.size(); i++) { - if (!custom_effects[i].is_valid()) { + Ref<RichTextEffect> effect = custom_effects[i]; + if (!effect.is_valid()) { continue; } - if (custom_effects[i]->get_bbcode() == p_bbcode_identifier) { - return custom_effects[i]; + if (effect->get_bbcode() == p_bbcode_identifier) { + return effect; } } @@ -4326,7 +4353,7 @@ RichTextLabel::RichTextLabel() { current_frame = main; vscroll = memnew(VScrollBar); - add_child(vscroll); + add_child(vscroll, false, INTERNAL_MODE_FRONT); vscroll->set_drag_node(String("..")); vscroll->set_step(1); vscroll->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, 0); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index 999d8b05fd..f25a8bf193 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -161,7 +161,7 @@ private: struct ItemImage : public Item { Ref<Texture2D> image; - VAlign inline_align = VALIGN_TOP; + InlineAlign inline_align = INLINE_ALIGN_CENTER; Size2 size; Color color; ItemImage() { type = ITEM_IMAGE; } @@ -248,7 +248,7 @@ private: int total_width = 0; int total_height = 0; - VAlign inline_align = VALIGN_TOP; + InlineAlign inline_align = INLINE_ALIGN_TOP; ItemTable() { type = ITEM_TABLE; } }; @@ -260,7 +260,7 @@ private: }; struct ItemFX : public Item { - float elapsed_time = 0.f; + double elapsed_time = 0.f; }; struct ItemShake : public ItemFX { @@ -363,7 +363,7 @@ private: ItemMeta *meta_hovering = nullptr; Variant current_meta; - Vector<Ref<RichTextEffect>> custom_effects; + Array custom_effects; void _invalidate_current_line(ItemFrame *p_frame); void _validate_line_caches(ItemFrame *p_frame); @@ -440,10 +440,10 @@ private: void _fetch_item_fx_stack(Item *p_item, Vector<ItemFX *> &r_stack); void _update_scroll(); - void _update_fx(ItemFrame *p_frame, float p_delta_time); + void _update_fx(ItemFrame *p_frame, double p_delta_time); void _scroll_changed(double); - void _gui_input(Ref<InputEvent> p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; Item *_get_next_item(Item *p_item, bool p_free = false) const; Item *_get_prev_item(Item *p_item, bool p_free = false) const; @@ -463,7 +463,7 @@ private: public: String get_text(); void add_text(const String &p_text); - void add_image(const Ref<Texture2D> &p_image, const int p_width = 0, const int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), VAlign p_align = VALIGN_TOP); + void add_image(const Ref<Texture2D> &p_image, const int p_width = 0, const int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlign p_align = INLINE_ALIGN_CENTER); void add_newline(); bool remove_line(const int p_line); 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)); @@ -484,7 +484,7 @@ public: 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); - void push_table(int p_columns, VAlign p_align = VALIGN_TOP); + void push_table(int p_columns, InlineAlign p_align = INLINE_ALIGN_TOP); void push_fade(int p_start_index, int p_length); void push_shake(int p_strength, float p_rate); void push_wave(float p_frequency, float p_amplitude); @@ -577,8 +577,8 @@ public: void set_percent_visible(float p_percent); float get_percent_visible() const; - void set_effects(const Vector<Variant> &effects); - Vector<Variant> get_effects(); + void set_effects(Array p_effects); + Array get_effects(); void install_effect(const Variant effect); diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index 62276e3af0..4edf373fbf 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -41,12 +41,12 @@ void ScrollBar::set_can_focus_by_default(bool p_can_focus) { focus_by_default = p_can_focus; } -void ScrollBar::_gui_input(Ref<InputEvent> p_event) { +void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventMouseMotion> m = p_event; if (!m.is_valid() || drag.active) { - emit_signal("scrolling"); + emit_signal(SNAME("scrolling")); } Ref<InputEventMouseButton> b = p_event; @@ -70,8 +70,8 @@ void ScrollBar::_gui_input(Ref<InputEvent> p_event) { if (b->is_pressed()) { double ofs = orientation == VERTICAL ? b->get_position().y : b->get_position().x; - Ref<Texture2D> decr = get_theme_icon("decrement"); - Ref<Texture2D> incr = get_theme_icon("increment"); + Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); + Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); double decr_size = orientation == VERTICAL ? decr->get_height() : decr->get_width(); double incr_size = orientation == VERTICAL ? incr->get_height() : incr->get_width(); @@ -80,12 +80,16 @@ void ScrollBar::_gui_input(Ref<InputEvent> p_event) { double total = orientation == VERTICAL ? get_size().height : get_size().width; if (ofs < decr_size) { + decr_active = true; set_value(get_value() - (custom_step >= 0 ? custom_step : get_step())); + update(); return; } if (ofs > total - incr_size) { + incr_active = true; set_value(get_value() + (custom_step >= 0 ? custom_step : get_step())); + update(); return; } @@ -130,6 +134,8 @@ void ScrollBar::_gui_input(Ref<InputEvent> p_event) { } } else { + incr_active = false; + decr_active = false; drag.active = false; update(); } @@ -140,7 +146,7 @@ void ScrollBar::_gui_input(Ref<InputEvent> p_event) { if (drag.active) { double ofs = orientation == VERTICAL ? m->get_position().y : m->get_position().x; - Ref<Texture2D> decr = get_theme_icon("decrement"); + Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); double decr_size = orientation == VERTICAL ? decr->get_height() : decr->get_width(); ofs -= decr_size; @@ -150,8 +156,8 @@ void ScrollBar::_gui_input(Ref<InputEvent> p_event) { set_as_ratio(drag.value_at_click + diff); } else { double ofs = orientation == VERTICAL ? m->get_position().y : m->get_position().x; - Ref<Texture2D> decr = get_theme_icon("decrement"); - Ref<Texture2D> incr = get_theme_icon("increment"); + Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); + Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); double decr_size = orientation == VERTICAL ? decr->get_height() : decr->get_width(); double incr_size = orientation == VERTICAL ? incr->get_height() : incr->get_width(); @@ -215,17 +221,33 @@ void ScrollBar::_notification(int p_what) { if (p_what == NOTIFICATION_DRAW) { RID ci = get_canvas_item(); - Ref<Texture2D> decr = highlight == HIGHLIGHT_DECR ? get_theme_icon("decrement_highlight") : get_theme_icon("decrement"); - Ref<Texture2D> incr = highlight == HIGHLIGHT_INCR ? get_theme_icon("increment_highlight") : get_theme_icon("increment"); - Ref<StyleBox> bg = has_focus() ? get_theme_stylebox("scroll_focus") : get_theme_stylebox("scroll"); + Ref<Texture2D> decr, incr; + + if (decr_active) { + decr = get_theme_icon(SNAME("decrement_pressed")); + } else if (highlight == HIGHLIGHT_DECR) { + decr = get_theme_icon(SNAME("decrement_highlight")); + } else { + decr = get_theme_icon(SNAME("decrement")); + } + + if (incr_active) { + incr = get_theme_icon(SNAME("increment_pressed")); + } else if (highlight == HIGHLIGHT_INCR) { + incr = get_theme_icon(SNAME("increment_highlight")); + } else { + incr = get_theme_icon(SNAME("increment")); + } + + Ref<StyleBox> bg = has_focus() ? get_theme_stylebox(SNAME("scroll_focus")) : get_theme_stylebox(SNAME("scroll")); Ref<StyleBox> grabber; if (drag.active) { - grabber = get_theme_stylebox("grabber_pressed"); + grabber = get_theme_stylebox(SNAME("grabber_pressed")); } else if (highlight == HIGHLIGHT_RANGE) { - grabber = get_theme_stylebox("grabber_highlight"); + grabber = get_theme_stylebox(SNAME("grabber_highlight")); } else { - grabber = get_theme_stylebox("grabber"); + grabber = get_theme_stylebox(SNAME("grabber")); } Point2 ofs; @@ -389,7 +411,7 @@ void ScrollBar::_notification(int p_what) { } double ScrollBar::get_grabber_min_size() const { - Ref<StyleBox> grabber = get_theme_stylebox("grabber"); + Ref<StyleBox> grabber = get_theme_stylebox(SNAME("grabber")); Size2 gminsize = grabber->get_minimum_size() + grabber->get_center_size(); return (orientation == VERTICAL) ? gminsize.height : gminsize.width; } @@ -415,17 +437,17 @@ double ScrollBar::get_area_size() const { switch (orientation) { case VERTICAL: { double area = get_size().height; - area -= get_theme_stylebox("scroll")->get_minimum_size().height; - area -= get_theme_icon("increment")->get_height(); - area -= get_theme_icon("decrement")->get_height(); + area -= get_theme_stylebox(SNAME("scroll"))->get_minimum_size().height; + area -= get_theme_icon(SNAME("increment"))->get_height(); + area -= get_theme_icon(SNAME("decrement"))->get_height(); area -= get_grabber_min_size(); return area; } break; case HORIZONTAL: { double area = get_size().width; - area -= get_theme_stylebox("scroll")->get_minimum_size().width; - area -= get_theme_icon("increment")->get_width(); - area -= get_theme_icon("decrement")->get_width(); + area -= get_theme_stylebox(SNAME("scroll"))->get_minimum_size().width; + area -= get_theme_icon(SNAME("increment"))->get_width(); + area -= get_theme_icon(SNAME("decrement"))->get_width(); area -= get_grabber_min_size(); return area; } break; @@ -439,13 +461,13 @@ double ScrollBar::get_area_offset() const { double ofs = 0.0; if (orientation == VERTICAL) { - ofs += get_theme_stylebox("hscroll")->get_margin(SIDE_TOP); - ofs += get_theme_icon("decrement")->get_height(); + ofs += get_theme_stylebox(SNAME("hscroll"))->get_margin(SIDE_TOP); + ofs += get_theme_icon(SNAME("decrement"))->get_height(); } if (orientation == HORIZONTAL) { - ofs += get_theme_stylebox("hscroll")->get_margin(SIDE_LEFT); - ofs += get_theme_icon("decrement")->get_width(); + ofs += get_theme_stylebox(SNAME("hscroll"))->get_margin(SIDE_LEFT); + ofs += get_theme_icon(SNAME("decrement"))->get_width(); } return ofs; @@ -456,9 +478,9 @@ double ScrollBar::get_grabber_offset() const { } Size2 ScrollBar::get_minimum_size() const { - Ref<Texture2D> incr = get_theme_icon("increment"); - Ref<Texture2D> decr = get_theme_icon("decrement"); - Ref<StyleBox> bg = get_theme_stylebox("scroll"); + Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); + Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); + Ref<StyleBox> bg = get_theme_stylebox(SNAME("scroll")); Size2 minsize; if (orientation == VERTICAL) { @@ -597,7 +619,6 @@ bool ScrollBar::is_smooth_scroll_enabled() const { } void ScrollBar::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &ScrollBar::_gui_input); ClassDB::bind_method(D_METHOD("set_custom_step", "step"), &ScrollBar::set_custom_step); ClassDB::bind_method(D_METHOD("get_custom_step"), &ScrollBar::get_custom_step); diff --git a/scene/gui/scroll_bar.h b/scene/gui/scroll_bar.h index 24b3b33e82..574d17ee20 100644 --- a/scene/gui/scroll_bar.h +++ b/scene/gui/scroll_bar.h @@ -51,6 +51,9 @@ class ScrollBar : public Range { HighlightStatus highlight = HIGHLIGHT_NONE; + bool incr_active = false; + bool decr_active = false; + struct Drag { bool active = false; float pos_at_click = 0.0; @@ -86,7 +89,7 @@ class ScrollBar : public Range { void _drag_node_exit(); void _drag_node_input(const Ref<InputEvent> &p_input); - void _gui_input(Ref<InputEvent> p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; protected: void _notification(int p_what); diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index 177f146b6a..0c051f61e2 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -33,7 +33,7 @@ #include "scene/main/window.h" Size2 ScrollContainer::get_minimum_size() const { - Ref<StyleBox> sb = get_theme_stylebox("bg"); + Ref<StyleBox> sb = get_theme_stylebox(SNAME("bg")); Size2 min_size; for (int i = 0; i < get_child_count(); i++) { @@ -77,13 +77,13 @@ void ScrollContainer::_cancel_drag() { drag_from = Vector2(); if (beyond_deadzone) { - emit_signal("scroll_ended"); + emit_signal(SNAME("scroll_ended")); propagate_notification(NOTIFICATION_SCROLL_END); beyond_deadzone = false; } } -void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) { +void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { ERR_FAIL_COND(p_gui_input.is_null()); double prev_v_scroll = v_scroll->get_value(); @@ -173,7 +173,7 @@ void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) { if (beyond_deadzone || (scroll_h && Math::abs(drag_accum.x) > deadzone) || (scroll_v && Math::abs(drag_accum.y) > deadzone)) { if (!beyond_deadzone) { propagate_notification(NOTIFICATION_SCROLL_BEGIN); - emit_signal("scroll_started"); + emit_signal(SNAME("scroll_started")); beyond_deadzone = true; // resetting drag_accum here ensures smooth scrolling after reaching deadzone @@ -228,9 +228,6 @@ void ScrollContainer::_update_scrollbar_position() { v_scroll->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, 0); v_scroll->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0); - h_scroll->raise(); - v_scroll->raise(); - _updating_scrollbars = false; } @@ -260,7 +257,7 @@ void ScrollContainer::_update_dimensions() { Size2 size = get_size(); Point2 ofs; - Ref<StyleBox> sb = get_theme_stylebox("bg"); + Ref<StyleBox> sb = get_theme_stylebox(SNAME("bg")); size -= sb->get_minimum_size(); ofs += sb->get_offset(); bool rtl = is_layout_rtl(); @@ -319,7 +316,7 @@ void ScrollContainer::_update_dimensions() { void ScrollContainer::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_LAYOUT_DIRECTION_CHANGED || p_what == NOTIFICATION_TRANSLATION_CHANGED) { _updating_scrollbars = true; - call_deferred("_update_scrollbar_position"); + call_deferred(SNAME("_update_scrollbar_position")); }; if (p_what == NOTIFICATION_READY) { @@ -332,7 +329,7 @@ void ScrollContainer::_notification(int p_what) { }; if (p_what == NOTIFICATION_DRAW) { - Ref<StyleBox> sb = get_theme_stylebox("bg"); + Ref<StyleBox> sb = get_theme_stylebox(SNAME("bg")); draw_style_box(sb, Rect2(Vector2(), get_size())); update_scrollbars(); @@ -409,7 +406,7 @@ void ScrollContainer::_notification(int p_what) { void ScrollContainer::update_scrollbars() { Size2 size = get_size(); - Ref<StyleBox> sb = get_theme_stylebox("bg"); + Ref<StyleBox> sb = get_theme_stylebox(SNAME("bg")); size -= sb->get_minimum_size(); Size2 hmin; @@ -568,7 +565,6 @@ VScrollBar *ScrollContainer::get_v_scrollbar() { } void ScrollContainer::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &ScrollContainer::_gui_input); ClassDB::bind_method(D_METHOD("_update_scrollbar_position"), &ScrollContainer::_update_scrollbar_position); ClassDB::bind_method(D_METHOD("set_h_scroll", "value"), &ScrollContainer::set_h_scroll); @@ -619,12 +615,12 @@ void ScrollContainer::_bind_methods() { ScrollContainer::ScrollContainer() { h_scroll = memnew(HScrollBar); h_scroll->set_name("_h_scroll"); - add_child(h_scroll); + add_child(h_scroll, false, INTERNAL_MODE_BACK); h_scroll->connect("value_changed", callable_mp(this, &ScrollContainer::_scroll_moved)); v_scroll = memnew(VScrollBar); v_scroll->set_name("_v_scroll"); - add_child(v_scroll); + add_child(v_scroll, false, INTERNAL_MODE_BACK); v_scroll->connect("value_changed", callable_mp(this, &ScrollContainer::_scroll_moved)); deadzone = GLOBAL_GET("gui/common/default_scroll_deadzone"); diff --git a/scene/gui/scroll_container.h b/scene/gui/scroll_container.h index 4733fdabca..9f4ec558dc 100644 --- a/scene/gui/scroll_container.h +++ b/scene/gui/scroll_container.h @@ -68,7 +68,7 @@ class ScrollContainer : public Container { protected: Size2 get_minimum_size() const override; - void _gui_input(const Ref<InputEvent> &p_gui_input); + virtual void gui_input(const Ref<InputEvent> &p_gui_input) override; void _gui_focus_changed(Control *p_control); void _update_dimensions(); void _notification(int p_what); diff --git a/scene/gui/separator.cpp b/scene/gui/separator.cpp index 3cb8ccf135..1f3cb7aa24 100644 --- a/scene/gui/separator.cpp +++ b/scene/gui/separator.cpp @@ -33,9 +33,9 @@ Size2 Separator::get_minimum_size() const { Size2 ms(3, 3); if (orientation == VERTICAL) { - ms.x = get_theme_constant("separation"); + ms.x = get_theme_constant(SNAME("separation")); } else { // HORIZONTAL - ms.y = get_theme_constant("separation"); + ms.y = get_theme_constant(SNAME("separation")); } return ms; } @@ -44,7 +44,7 @@ void Separator::_notification(int p_what) { switch (p_what) { case NOTIFICATION_DRAW: { Size2i size = get_size(); - Ref<StyleBox> style = get_theme_stylebox("separator"); + Ref<StyleBox> style = get_theme_stylebox(SNAME("separator")); Size2i ssize = style->get_minimum_size() + style->get_center_size(); if (orientation == VERTICAL) { diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp index 5947f3b99e..352f87954e 100644 --- a/scene/gui/slider.cpp +++ b/scene/gui/slider.cpp @@ -32,10 +32,10 @@ #include "core/os/keyboard.h" Size2 Slider::get_minimum_size() const { - Ref<StyleBox> style = get_theme_stylebox("slider"); + Ref<StyleBox> style = get_theme_stylebox(SNAME("slider")); Size2i ss = style->get_minimum_size() + style->get_center_size(); - Ref<Texture2D> grabber = get_theme_icon("grabber"); + Ref<Texture2D> grabber = get_theme_icon(SNAME("grabber")); Size2i rs = grabber->get_size(); if (orientation == HORIZONTAL) { @@ -45,7 +45,7 @@ Size2 Slider::get_minimum_size() const { } } -void Slider::_gui_input(Ref<InputEvent> p_event) { +void Slider::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (!editable) { @@ -89,7 +89,7 @@ void Slider::_gui_input(Ref<InputEvent> p_event) { if (mm.is_valid()) { if (grab.active) { Size2i size = get_size(); - Ref<Texture2D> grabber = get_theme_icon("grabber"); + Ref<Texture2D> grabber = get_theme_icon(SNAME("grabber")); float motion = (orientation == VERTICAL ? mm->get_position().y : mm->get_position().x) - grab.pos; if (orientation == VERTICAL) { motion = -motion; @@ -161,11 +161,11 @@ void Slider::_notification(int p_what) { case NOTIFICATION_DRAW: { RID ci = get_canvas_item(); Size2i size = get_size(); - Ref<StyleBox> style = get_theme_stylebox("slider"); + Ref<StyleBox> style = get_theme_stylebox(SNAME("slider")); bool highlighted = mouse_inside || has_focus(); Ref<StyleBox> grabber_area = get_theme_stylebox(highlighted ? "grabber_area_highlight" : "grabber_area"); Ref<Texture2D> grabber = get_theme_icon(editable ? (highlighted ? "grabber_highlight" : "grabber") : "grabber_disabled"); - Ref<Texture2D> tick = get_theme_icon("tick"); + Ref<Texture2D> tick = get_theme_icon(SNAME("tick")); double ratio = Math::is_nan(get_as_ratio()) ? 0 : get_as_ratio(); if (orientation == VERTICAL) { @@ -253,7 +253,6 @@ bool Slider::is_scrollable() const { } void Slider::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &Slider::_gui_input); ClassDB::bind_method(D_METHOD("set_ticks", "count"), &Slider::set_ticks); ClassDB::bind_method(D_METHOD("get_ticks"), &Slider::get_ticks); diff --git a/scene/gui/slider.h b/scene/gui/slider.h index 65a4036cd1..46fa08bbf0 100644 --- a/scene/gui/slider.h +++ b/scene/gui/slider.h @@ -50,7 +50,7 @@ class Slider : public Range { bool scrollable = true; protected: - void _gui_input(Ref<InputEvent> p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; void _notification(int p_what); static void _bind_methods(); bool ticks_on_borders = false; diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index 941dd30057..1074d0d8a0 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -99,7 +99,7 @@ void SpinBox::_release_mouse() { } } -void SpinBox::_gui_input(const Ref<InputEvent> &p_event) { +void SpinBox::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (!is_editable()) { @@ -170,7 +170,7 @@ void SpinBox::_gui_input(const Ref<InputEvent> &p_event) { void SpinBox::_line_edit_focus_exit() { // discontinue because the focus_exit was caused by right-click context menu - if (line_edit->get_menu()->is_visible()) { + if (line_edit->is_menu_visible()) { return; } @@ -188,7 +188,7 @@ inline void SpinBox::_adjust_width_for_icon(const Ref<Texture2D> &icon) { void SpinBox::_notification(int p_what) { if (p_what == NOTIFICATION_DRAW) { - Ref<Texture2D> updown = get_theme_icon("updown"); + Ref<Texture2D> updown = get_theme_icon(SNAME("updown")); _adjust_width_for_icon(updown); @@ -204,15 +204,15 @@ void SpinBox::_notification(int p_what) { } else if (p_what == NOTIFICATION_FOCUS_EXIT) { //_value_changed(0); } else if (p_what == NOTIFICATION_ENTER_TREE) { - _adjust_width_for_icon(get_theme_icon("updown")); + _adjust_width_for_icon(get_theme_icon(SNAME("updown"))); _value_changed(0); } else if (p_what == NOTIFICATION_EXIT_TREE) { _release_mouse(); } else if (p_what == NOTIFICATION_TRANSLATION_CHANGED) { _value_changed(0); } else if (p_what == NOTIFICATION_THEME_CHANGED) { - call_deferred("minimum_size_changed"); - get_line_edit()->call_deferred("minimum_size_changed"); + call_deferred(SNAME("minimum_size_changed")); + get_line_edit()->call_deferred(SNAME("minimum_size_changed")); } else if (p_what == NOTIFICATION_LAYOUT_DIRECTION_CHANGED || p_what == NOTIFICATION_TRANSLATION_CHANGED) { update(); } @@ -258,7 +258,7 @@ void SpinBox::apply() { void SpinBox::_bind_methods() { //ClassDB::bind_method(D_METHOD("_value_changed"),&SpinBox::_value_changed); - ClassDB::bind_method(D_METHOD("_gui_input"), &SpinBox::_gui_input); + ClassDB::bind_method(D_METHOD("set_align", "align"), &SpinBox::set_align); ClassDB::bind_method(D_METHOD("get_align"), &SpinBox::get_align); ClassDB::bind_method(D_METHOD("set_suffix", "suffix"), &SpinBox::set_suffix); @@ -278,7 +278,7 @@ void SpinBox::_bind_methods() { SpinBox::SpinBox() { line_edit = memnew(LineEdit); - add_child(line_edit); + add_child(line_edit, false, INTERNAL_MODE_FRONT); line_edit->set_anchors_and_offsets_preset(Control::PRESET_WIDE); line_edit->set_mouse_filter(MOUSE_FILTER_PASS); @@ -291,5 +291,5 @@ SpinBox::SpinBox() { range_click_timer = memnew(Timer); range_click_timer->connect("timeout", callable_mp(this, &SpinBox::_range_click_timeout)); - add_child(range_click_timer); + add_child(range_click_timer, false, INTERNAL_MODE_FRONT); } diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h index fb10379296..9ec3885f1f 100644 --- a/scene/gui/spin_box.h +++ b/scene/gui/spin_box.h @@ -65,7 +65,7 @@ class SpinBox : public Range { inline void _adjust_width_for_icon(const Ref<Texture2D> &icon); protected: - void _gui_input(const Ref<InputEvent> &p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; void _notification(int p_what); diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp index 9796b32c6b..4736a1ad37 100644 --- a/scene/gui/split_container.cpp +++ b/scene/gui/split_container.cpp @@ -76,8 +76,8 @@ void SplitContainer::_resort() { bool second_expanded = (vertical ? second->get_v_size_flags() : second->get_h_size_flags()) & SIZE_EXPAND; // Determine the separation between items - Ref<Texture2D> g = get_theme_icon("grabber"); - int sep = get_theme_constant("separation"); + Ref<Texture2D> g = get_theme_icon(SNAME("grabber")); + int sep = get_theme_constant(SNAME("separation")); sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(sep, vertical ? g->get_height() : g->get_width()) : 0; // Compute the minimum size @@ -131,8 +131,8 @@ Size2 SplitContainer::get_minimum_size() const { /* Calculate MINIMUM SIZE */ Size2i minimum; - Ref<Texture2D> g = get_theme_icon("grabber"); - int sep = get_theme_constant("separation"); + Ref<Texture2D> g = get_theme_icon(SNAME("grabber")); + int sep = get_theme_constant(SNAME("separation")); sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(sep, vertical ? g->get_height() : g->get_width()) : 0; for (int i = 0; i < 2; i++) { @@ -173,7 +173,7 @@ void SplitContainer::_notification(int p_what) { } break; case NOTIFICATION_MOUSE_EXIT: { mouse_inside = false; - if (get_theme_constant("autohide")) { + if (get_theme_constant(SNAME("autohide"))) { update(); } } break; @@ -182,7 +182,7 @@ void SplitContainer::_notification(int p_what) { return; } - if (collapsed || (!dragging && !mouse_inside && get_theme_constant("autohide"))) { + if (collapsed || (!dragging && !mouse_inside && get_theme_constant(SNAME("autohide")))) { return; } @@ -190,8 +190,8 @@ void SplitContainer::_notification(int p_what) { return; } - int sep = dragger_visibility != DRAGGER_HIDDEN_COLLAPSED ? get_theme_constant("separation") : 0; - Ref<Texture2D> tex = get_theme_icon("grabber"); + int sep = dragger_visibility != DRAGGER_HIDDEN_COLLAPSED ? get_theme_constant(SNAME("separation")) : 0; + Ref<Texture2D> tex = get_theme_icon(SNAME("grabber")); Size2 size = get_size(); if (vertical) { @@ -206,7 +206,7 @@ void SplitContainer::_notification(int p_what) { } } -void SplitContainer::_gui_input(const Ref<InputEvent> &p_event) { +void SplitContainer::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (collapsed || !_getch(0) || !_getch(1) || dragger_visibility != DRAGGER_VISIBLE) { @@ -218,7 +218,7 @@ void SplitContainer::_gui_input(const Ref<InputEvent> &p_event) { if (mb.is_valid()) { if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { if (mb->is_pressed()) { - int sep = get_theme_constant("separation"); + int sep = get_theme_constant(SNAME("separation")); if (vertical) { if (mb->get_position().y > middle_sep && mb->get_position().y < middle_sep + sep) { @@ -244,14 +244,14 @@ void SplitContainer::_gui_input(const Ref<InputEvent> &p_event) { if (mm.is_valid()) { bool mouse_inside_state = false; if (vertical) { - mouse_inside_state = mm->get_position().y > middle_sep && mm->get_position().y < middle_sep + get_theme_constant("separation"); + mouse_inside_state = mm->get_position().y > middle_sep && mm->get_position().y < middle_sep + get_theme_constant(SNAME("separation")); } else { - mouse_inside_state = mm->get_position().x > middle_sep && mm->get_position().x < middle_sep + get_theme_constant("separation"); + mouse_inside_state = mm->get_position().x > middle_sep && mm->get_position().x < middle_sep + get_theme_constant(SNAME("separation")); } if (mouse_inside != mouse_inside_state) { mouse_inside = mouse_inside_state; - if (get_theme_constant("autohide")) { + if (get_theme_constant(SNAME("autohide"))) { update(); } } @@ -267,7 +267,7 @@ void SplitContainer::_gui_input(const Ref<InputEvent> &p_event) { } should_clamp_split_offset = true; queue_sort(); - emit_signal("dragged", get_split_offset()); + emit_signal(SNAME("dragged"), get_split_offset()); } } @@ -277,7 +277,7 @@ Control::CursorShape SplitContainer::get_cursor_shape(const Point2 &p_pos) const } if (!collapsed && _getch(0) && _getch(1) && dragger_visibility == DRAGGER_VISIBLE) { - int sep = get_theme_constant("separation"); + int sep = get_theme_constant(SNAME("separation")); if (vertical) { if (p_pos.y > middle_sep && p_pos.y < middle_sep + sep) { @@ -337,8 +337,6 @@ bool SplitContainer::is_collapsed() const { } void SplitContainer::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &SplitContainer::_gui_input); - ClassDB::bind_method(D_METHOD("set_split_offset", "offset"), &SplitContainer::set_split_offset); ClassDB::bind_method(D_METHOD("get_split_offset"), &SplitContainer::get_split_offset); ClassDB::bind_method(D_METHOD("clamp_split_offset"), &SplitContainer::clamp_split_offset); diff --git a/scene/gui/split_container.h b/scene/gui/split_container.h index 6cb94d6ecf..47fd30a122 100644 --- a/scene/gui/split_container.h +++ b/scene/gui/split_container.h @@ -60,7 +60,7 @@ private: void _resort(); protected: - void _gui_input(const Ref<InputEvent> &p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp index bfc7e29f9c..53ea32e1b7 100644 --- a/scene/gui/subviewport_container.cpp +++ b/scene/gui/subviewport_container.cpp @@ -139,7 +139,7 @@ void SubViewportContainer::_notification(int p_what) { } } -void SubViewportContainer::_input(const Ref<InputEvent> &p_event) { +void SubViewportContainer::input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (Engine::get_singleton()->is_editor_hint()) { @@ -162,11 +162,11 @@ void SubViewportContainer::_input(const Ref<InputEvent> &p_event) { continue; } - c->input(ev); + c->push_input(ev); } } -void SubViewportContainer::_unhandled_input(const Ref<InputEvent> &p_event) { +void SubViewportContainer::unhandled_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (Engine::get_singleton()->is_editor_hint()) { @@ -189,13 +189,11 @@ void SubViewportContainer::_unhandled_input(const Ref<InputEvent> &p_event) { continue; } - c->unhandled_input(ev); + c->push_unhandled_input(ev); } } void SubViewportContainer::_bind_methods() { - ClassDB::bind_method(D_METHOD("_unhandled_input", "event"), &SubViewportContainer::_unhandled_input); - ClassDB::bind_method(D_METHOD("_input", "event"), &SubViewportContainer::_input); ClassDB::bind_method(D_METHOD("set_stretch", "enable"), &SubViewportContainer::set_stretch); ClassDB::bind_method(D_METHOD("is_stretch_enabled"), &SubViewportContainer::is_stretch_enabled); diff --git a/scene/gui/subviewport_container.h b/scene/gui/subviewport_container.h index 77cf4c16b3..7853f1590e 100644 --- a/scene/gui/subviewport_container.h +++ b/scene/gui/subviewport_container.h @@ -47,8 +47,8 @@ public: void set_stretch(bool p_enable); bool is_stretch_enabled() const; - void _input(const Ref<InputEvent> &p_event); - void _unhandled_input(const Ref<InputEvent> &p_event); + virtual void input(const Ref<InputEvent> &p_event) override; + virtual void unhandled_input(const Ref<InputEvent> &p_event) override; void set_stretch_shrink(int p_shrink); int get_stretch_shrink() const; diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index 133966013b..137ce7e96f 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -43,9 +43,9 @@ int TabContainer::_get_top_margin() const { } // Respect the minimum tab height. - Ref<StyleBox> tab_unselected = get_theme_stylebox("tab_unselected"); - Ref<StyleBox> tab_selected = get_theme_stylebox("tab_selected"); - Ref<StyleBox> tab_disabled = get_theme_stylebox("tab_disabled"); + 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 tab_height = MAX(MAX(tab_unselected->get_minimum_size().height, tab_selected->get_minimum_size().height), tab_disabled->get_minimum_size().height); @@ -71,7 +71,7 @@ int TabContainer::_get_top_margin() const { return tab_height + content_height; } -void TabContainer::_gui_input(const Ref<InputEvent> &p_event) { +void TabContainer::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventMouseButton> mb = p_event; @@ -88,11 +88,11 @@ void TabContainer::_gui_input(const Ref<InputEvent> &p_event) { } // Handle menu button. - Ref<Texture2D> menu = get_theme_icon("menu"); + Ref<Texture2D> menu = get_theme_icon(SNAME("menu")); if (is_layout_rtl()) { if (popup && pos.x < menu->get_width()) { - emit_signal("pre_popup_pressed"); + emit_signal(SNAME("pre_popup_pressed")); Vector2 popup_pos = get_screen_position(); popup_pos.y += menu->get_height(); @@ -103,7 +103,7 @@ void TabContainer::_gui_input(const Ref<InputEvent> &p_event) { } } else { if (popup && pos.x > size.width - menu->get_width()) { - emit_signal("pre_popup_pressed"); + emit_signal(SNAME("pre_popup_pressed")); Vector2 popup_pos = get_screen_position(); popup_pos.x += size.width - popup->get_size().width; @@ -129,8 +129,8 @@ void TabContainer::_gui_input(const Ref<InputEvent> &p_event) { popup_ofs = menu->get_width(); } - Ref<Texture2D> increment = get_theme_icon("increment"); - Ref<Texture2D> decrement = get_theme_icon("decrement"); + Ref<Texture2D> increment = get_theme_icon(SNAME("increment")); + Ref<Texture2D> decrement = get_theme_icon(SNAME("decrement")); if (is_layout_rtl()) { if (pos.x < popup_ofs + decrement->get_width()) { if (last_tab_cache < tabs.size() - 1) { @@ -203,7 +203,7 @@ void TabContainer::_gui_input(const Ref<InputEvent> &p_event) { return; } - Ref<Texture2D> menu = get_theme_icon("menu"); + Ref<Texture2D> menu = get_theme_icon(SNAME("menu")); if (popup) { if (is_layout_rtl()) { if (pos.x <= menu->get_width()) { @@ -248,8 +248,8 @@ void TabContainer::_gui_input(const Ref<InputEvent> &p_event) { popup_ofs = menu->get_width(); } - Ref<Texture2D> increment = get_theme_icon("increment"); - Ref<Texture2D> decrement = get_theme_icon("decrement"); + Ref<Texture2D> increment = get_theme_icon(SNAME("increment")); + Ref<Texture2D> decrement = get_theme_icon(SNAME("decrement")); if (is_layout_rtl()) { if (pos.x <= popup_ofs + decrement->get_width()) { @@ -289,10 +289,10 @@ void TabContainer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_RESIZED: { Vector<Control *> tabs = _get_tabs(); - int side_margin = get_theme_constant("side_margin"); - Ref<Texture2D> menu = get_theme_icon("menu"); - Ref<Texture2D> increment = get_theme_icon("increment"); - Ref<Texture2D> decrement = get_theme_icon("decrement"); + int side_margin = get_theme_constant(SNAME("side_margin")); + Ref<Texture2D> menu = get_theme_icon(SNAME("menu")); + Ref<Texture2D> increment = get_theme_icon(SNAME("increment")); + Ref<Texture2D> decrement = get_theme_icon(SNAME("decrement")); int header_width = get_size().width - side_margin * 2; // Find the width of the header area. @@ -332,26 +332,26 @@ void TabContainer::_notification(int p_what) { bool rtl = is_layout_rtl(); // Draw only the tab area if the header is hidden. - Ref<StyleBox> panel = get_theme_stylebox("panel"); + Ref<StyleBox> panel = get_theme_stylebox(SNAME("panel")); if (!tabs_visible) { panel->draw(canvas, Rect2(0, 0, size.width, size.height)); return; } Vector<Control *> tabs = _get_tabs(); - Ref<StyleBox> tab_unselected = get_theme_stylebox("tab_unselected"); - Ref<StyleBox> tab_selected = get_theme_stylebox("tab_selected"); - Ref<StyleBox> tab_disabled = get_theme_stylebox("tab_disabled"); - Ref<Texture2D> increment = get_theme_icon("increment"); - Ref<Texture2D> increment_hl = get_theme_icon("increment_highlight"); - Ref<Texture2D> decrement = get_theme_icon("decrement"); - Ref<Texture2D> decrement_hl = get_theme_icon("decrement_highlight"); - Ref<Texture2D> menu = get_theme_icon("menu"); - Ref<Texture2D> menu_hl = get_theme_icon("menu_highlight"); - Color font_selected_color = get_theme_color("font_selected_color"); - Color font_unselected_color = get_theme_color("font_unselected_color"); - Color font_disabled_color = get_theme_color("font_disabled_color"); - int side_margin = get_theme_constant("side_margin"); + Ref<StyleBox> tab_unselected = get_theme_stylebox(SNAME("tab_unselected")); + Ref<StyleBox> tab_selected = get_theme_stylebox(SNAME("tab_selected")); + Ref<StyleBox> tab_disabled = get_theme_stylebox(SNAME("tab_disabled")); + Ref<Texture2D> increment = get_theme_icon(SNAME("increment")); + Ref<Texture2D> increment_hl = get_theme_icon(SNAME("increment_highlight")); + Ref<Texture2D> decrement = get_theme_icon(SNAME("decrement")); + Ref<Texture2D> decrement_hl = get_theme_icon(SNAME("decrement_highlight")); + Ref<Texture2D> menu = get_theme_icon(SNAME("menu")); + Ref<Texture2D> menu_hl = get_theme_icon(SNAME("menu_highlight")); + Color font_selected_color = get_theme_color(SNAME("font_selected_color")); + Color font_unselected_color = get_theme_color(SNAME("font_unselected_color")); + Color font_disabled_color = get_theme_color(SNAME("font_disabled_color")); + int side_margin = get_theme_constant(SNAME("side_margin")); // Find out start and width of the header area. int header_x = side_margin; @@ -434,14 +434,14 @@ void TabContainer::_notification(int p_what) { } int tab_width = tab_widths[i]; - if (get_tab_disabled(index)) { + if (index == current) { + x_current = x; + } else if (get_tab_disabled(index)) { if (rtl) { _draw_tab(tab_disabled, font_disabled_color, index, size.width - (tabs_ofs_cache + x) - tab_width); } else { _draw_tab(tab_disabled, font_disabled_color, index, tabs_ofs_cache + x); } - } else if (index == current) { - x_current = x; } else { if (rtl) { _draw_tab(tab_unselected, font_unselected_color, index, size.width - (tabs_ofs_cache + x) - tab_width); @@ -459,12 +459,13 @@ void TabContainer::_notification(int p_what) { panel->draw(canvas, Rect2(0, header_height, size.width, size.height - header_height)); } - // Draw selected tab in front. only draw selected tab when it's in visible range. + // Draw selected tab in front. Only draw selected tab when it's in visible range. if (tabs.size() > 0 && current - first_tab_cache < tab_widths.size() && current >= first_tab_cache) { + Ref<StyleBox> current_style_box = get_tab_disabled(current) ? tab_disabled : tab_selected; if (rtl) { - _draw_tab(tab_selected, font_selected_color, current, size.width - (tabs_ofs_cache + x_current) - tab_widths[current]); + _draw_tab(current_style_box, font_selected_color, current, size.width - (tabs_ofs_cache + x_current) - tab_widths[current]); } else { - _draw_tab(tab_selected, font_selected_color, current, tabs_ofs_cache + x_current); + _draw_tab(current_style_box, font_selected_color, current, tabs_ofs_cache + x_current); } } @@ -529,18 +530,18 @@ void TabContainer::_notification(int p_what) { text_buf.write[i]->clear(); } _theme_changing = true; - call_deferred("_on_theme_changed"); // Wait until all changed theme. + call_deferred(SNAME("_on_theme_changed")); // Wait until all changed theme. } break; } } void TabContainer::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_index, float p_x) { - Vector<Control *> tabs = _get_tabs(); + Control *control = get_tab_control(p_index); RID canvas = get_canvas_item(); - Ref<Font> font = get_theme_font("font"); - Color font_outline_color = get_theme_color("font_outline_color"); - int outline_size = get_theme_constant("outline_size"); - int icon_text_distance = get_theme_constant("icon_separation"); + Ref<Font> font = get_theme_font(SNAME("font")); + Color font_outline_color = get_theme_color(SNAME("font_outline_color")); + int outline_size = get_theme_constant(SNAME("outline_size")); + int icon_text_distance = get_theme_constant(SNAME("icon_separation")); int tab_width = _get_tab_width(p_index); int header_height = _get_top_margin(); @@ -549,8 +550,7 @@ void TabContainer::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, in p_tab_style->draw(canvas, tab_rect); // Draw the tab contents. - Control *control = Object::cast_to<Control>(tabs[p_index]); - String text = control->has_meta("_tab_name") ? String(tr(String(control->get_meta("_tab_name")))) : String(tr(control->get_name())); + String text = control->has_meta("_tab_name") ? String(atr(String(control->get_meta("_tab_name")))) : String(atr(control->get_name())); int x_content = tab_rect.position.x + p_tab_style->get_margin(SIDE_LEFT); int top_margin = p_tab_style->get_margin(SIDE_TOP); @@ -580,11 +580,12 @@ void TabContainer::_refresh_texts() { text_buf.clear(); Vector<Control *> tabs = _get_tabs(); bool rtl = is_layout_rtl(); - Ref<Font> font = get_theme_font("font"); - int font_size = get_theme_font_size("font_size"); + Ref<Font> font = get_theme_font(SNAME("font")); + int font_size = get_theme_font_size(SNAME("font_size")); for (int i = 0; i < tabs.size(); i++) { Control *control = Object::cast_to<Control>(tabs[i]); - String text = control->has_meta("_tab_name") ? String(tr(String(control->get_meta("_tab_name")))) : String(tr(control->get_name())); + String text = control->has_meta("_tab_name") ? String(atr(String(control->get_meta("_tab_name")))) : String(atr(control->get_name())); + Ref<TextLine> name; name.instantiate(); name->set_direction(rtl ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); @@ -609,7 +610,7 @@ void TabContainer::_on_theme_changed() { } void TabContainer::_repaint() { - Ref<StyleBox> sb = get_theme_stylebox("panel"); + Ref<StyleBox> sb = get_theme_stylebox(SNAME("panel")); Vector<Control *> tabs = _get_tabs(); for (int i = 0; i < tabs.size(); i++) { Control *c = tabs[i]; @@ -619,10 +620,10 @@ void TabContainer::_repaint() { if (tabs_visible) { c->set_offset(SIDE_TOP, _get_top_margin()); } - c->set_offset(Side(SIDE_TOP), c->get_offset(Side(SIDE_TOP)) + sb->get_margin(Side(SIDE_TOP))); - c->set_offset(Side(SIDE_LEFT), c->get_offset(Side(SIDE_LEFT)) + sb->get_margin(Side(SIDE_LEFT))); - c->set_offset(Side(SIDE_RIGHT), c->get_offset(Side(SIDE_RIGHT)) - sb->get_margin(Side(SIDE_RIGHT))); - c->set_offset(Side(SIDE_BOTTOM), c->get_offset(Side(SIDE_BOTTOM)) - sb->get_margin(Side(SIDE_BOTTOM))); + c->set_offset(SIDE_TOP, c->get_offset(SIDE_TOP) + sb->get_margin(SIDE_TOP)); + c->set_offset(SIDE_LEFT, c->get_offset(SIDE_LEFT) + sb->get_margin(SIDE_LEFT)); + c->set_offset(SIDE_RIGHT, c->get_offset(SIDE_RIGHT) - sb->get_margin(SIDE_RIGHT)); + c->set_offset(SIDE_BOTTOM, c->get_offset(SIDE_BOTTOM) - sb->get_margin(SIDE_BOTTOM)); } else { c->hide(); @@ -640,15 +641,15 @@ void TabContainer::_on_mouse_exited() { int TabContainer::_get_tab_width(int p_index) const { ERR_FAIL_INDEX_V(p_index, get_tab_count(), 0); - Control *control = Object::cast_to<Control>(_get_tabs()[p_index]); - if (!control || control->is_set_as_top_level() || get_tab_hidden(p_index)) { + Control *control = get_tab_control(p_index); + if (!control || get_tab_hidden(p_index)) { return 0; } // Get the width of the text displayed on the tab. - Ref<Font> font = get_theme_font("font"); - int font_size = get_theme_font_size("font_size"); - String text = control->has_meta("_tab_name") ? String(tr(String(control->get_meta("_tab_name")))) : String(tr(control->get_name())); + Ref<Font> font = get_theme_font(SNAME("font")); + int font_size = get_theme_font_size(SNAME("font_size")); + String text = control->has_meta("_tab_name") ? String(atr(String(control->get_meta("_tab_name")))) : String(atr(control->get_name())); int width = font->get_string_size(text, font_size).width; // Add space for a tab icon. @@ -657,15 +658,15 @@ int TabContainer::_get_tab_width(int p_index) const { if (icon.is_valid()) { width += icon->get_width(); if (text != "") { - width += get_theme_constant("icon_separation"); + width += get_theme_constant(SNAME("icon_separation")); } } } // Respect a minimum size. - Ref<StyleBox> tab_unselected = get_theme_stylebox("tab_unselected"); - Ref<StyleBox> tab_selected = get_theme_stylebox("tab_selected"); - Ref<StyleBox> tab_disabled = get_theme_stylebox("tab_disabled"); + 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")); if (get_tab_disabled(p_index)) { width += tab_disabled->get_minimum_size().width; } else if (p_index == current) { @@ -681,7 +682,7 @@ Vector<Control *> TabContainer::_get_tabs() 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_top_level_control()) { + if (!control || control->is_set_as_top_level()) { continue; } @@ -699,10 +700,7 @@ void TabContainer::add_child_notify(Node *p_child) { Container::add_child_notify(p_child); Control *c = Object::cast_to<Control>(p_child); - if (!c) { - return; - } - if (c->is_set_as_top_level()) { + if (!c || c->is_set_as_top_level()) { return; } @@ -715,7 +713,7 @@ void TabContainer::add_child_notify(Node *p_child) { c->hide(); } else { c->show(); - //call_deferred("set_current_tab",0); + //call_deferred(SNAME("set_current_tab"),0); first = true; current = 0; previous = 0; @@ -724,22 +722,27 @@ void TabContainer::add_child_notify(Node *p_child) { if (tabs_visible) { c->set_offset(SIDE_TOP, _get_top_margin()); } - Ref<StyleBox> sb = get_theme_stylebox("panel"); - c->set_offset(Side(SIDE_TOP), c->get_offset(Side(SIDE_TOP)) + sb->get_margin(Side(SIDE_TOP))); - c->set_offset(Side(SIDE_LEFT), c->get_offset(Side(SIDE_LEFT)) + sb->get_margin(Side(SIDE_LEFT))); - c->set_offset(Side(SIDE_RIGHT), c->get_offset(Side(SIDE_RIGHT)) - sb->get_margin(Side(SIDE_RIGHT))); - c->set_offset(Side(SIDE_BOTTOM), c->get_offset(Side(SIDE_BOTTOM)) - sb->get_margin(Side(SIDE_BOTTOM))); + Ref<StyleBox> sb = get_theme_stylebox(SNAME("panel")); + c->set_offset(SIDE_TOP, c->get_offset(SIDE_TOP) + sb->get_margin(SIDE_TOP)); + c->set_offset(SIDE_LEFT, c->get_offset(SIDE_LEFT) + sb->get_margin(SIDE_LEFT)); + c->set_offset(SIDE_RIGHT, c->get_offset(SIDE_RIGHT) - sb->get_margin(SIDE_RIGHT)); + c->set_offset(SIDE_BOTTOM, c->get_offset(SIDE_BOTTOM) - sb->get_margin(SIDE_BOTTOM)); update(); p_child->connect("renamed", callable_mp(this, &TabContainer::_child_renamed_callback)); if (first && is_inside_tree()) { - emit_signal("tab_changed", current); + emit_signal(SNAME("tab_changed"), current); } } void TabContainer::move_child_notify(Node *p_child) { Container::move_child_notify(p_child); - call_deferred("_update_current_tab"); - _refresh_texts(); + + Control *c = Object::cast_to<Control>(p_child); + if (!c || c->is_set_as_top_level()) { + return; + } + + _update_current_tab(); update(); } @@ -756,11 +759,11 @@ void TabContainer::set_current_tab(int p_current) { _repaint(); if (pending_previous == current) { - emit_signal("tab_selected", current); + emit_signal(SNAME("tab_selected"), current); } else { previous = pending_previous; - emit_signal("tab_selected", current); - emit_signal("tab_changed", current); + emit_signal(SNAME("tab_selected"), current); + emit_signal(SNAME("tab_changed"), current); } update(); @@ -784,22 +787,19 @@ Control *TabContainer::get_tab_control(int p_idx) const { } Control *TabContainer::get_current_tab_control() const { - Vector<Control *> tabs = _get_tabs(); - if (current >= 0 && current < tabs.size()) { - return tabs[current]; - } else { - return nullptr; - } + return get_tab_control(current); } void TabContainer::remove_child_notify(Node *p_child) { Container::remove_child_notify(p_child); - if (!Object::cast_to<Control>(p_child)) { + Control *c = Object::cast_to<Control>(p_child); + if (!c || c->is_set_as_top_level()) { return; } - call_deferred("_update_current_tab"); + // Defer the call because tab is not yet removed (remove_child_notify is called right before p_child is actually removed). + call_deferred(SNAME("_update_current_tab")); p_child->disconnect("renamed", callable_mp(this, &TabContainer::_child_renamed_callback)); @@ -807,10 +807,9 @@ void TabContainer::remove_child_notify(Node *p_child) { } void TabContainer::_update_current_tab() { - Vector<Control *> tabs = _get_tabs(); _refresh_texts(); - int tc = tabs.size(); + int tc = get_tab_count(); if (current >= tc) { current = tc - 1; } @@ -898,7 +897,7 @@ void TabContainer::drop_data(const Point2 &p_point, const Variant &p_data) { if (hover_now < 0) { hover_now = get_tab_count() - 1; } - move_child(get_tab_control(tab_from_id), hover_now); + move_child(get_tab_control(tab_from_id), get_tab_control(hover_now)->get_index()); set_current_tab(hover_now); } else if (get_tabs_rearrange_group() != -1) { // drag and drop between TabContainers @@ -907,13 +906,13 @@ void TabContainer::drop_data(const Point2 &p_point, const Variant &p_data) { if (from_tabc && from_tabc->get_tabs_rearrange_group() == get_tabs_rearrange_group()) { Control *moving_tabc = from_tabc->get_tab_control(tab_from_id); from_tabc->remove_child(moving_tabc); - add_child(moving_tabc); + add_child(moving_tabc, false, INTERNAL_MODE_FRONT); if (hover_now < 0) { hover_now = get_tab_count() - 1; } - move_child(moving_tabc, hover_now); + move_child(moving_tabc, get_tab_control(hover_now)->get_index()); set_current_tab(hover_now); - emit_signal("tab_changed", hover_now); + emit_signal(SNAME("tab_changed"), hover_now); } } } @@ -944,12 +943,12 @@ int TabContainer::get_tab_idx_at_point(const Point2 &p_point) const { Popup *popup = get_popup(); if (popup) { - Ref<Texture2D> menu = get_theme_icon("menu"); + Ref<Texture2D> menu = get_theme_icon(SNAME("menu")); button_ofs += menu->get_width(); } if (buttons_visible_cache) { - Ref<Texture2D> increment = get_theme_icon("increment"); - Ref<Texture2D> decrement = get_theme_icon("decrement"); + Ref<Texture2D> increment = get_theme_icon(SNAME("increment")); + Ref<Texture2D> decrement = get_theme_icon(SNAME("decrement")); button_ofs += increment->get_width() + decrement->get_width(); } if (px > size.width - button_ofs) { @@ -1018,12 +1017,8 @@ bool TabContainer::is_all_tabs_in_front() const { return all_tabs_in_front; } -Control *TabContainer::_get_tab(int p_idx) const { - return get_tab_control(p_idx); -} - void TabContainer::set_tab_title(int p_tab, const String &p_title) { - Control *child = _get_tab(p_tab); + Control *child = get_tab_control(p_tab); ERR_FAIL_COND(!child); child->set_meta("_tab_name", p_title); _refresh_texts(); @@ -1031,7 +1026,7 @@ void TabContainer::set_tab_title(int p_tab, const String &p_title) { } String TabContainer::get_tab_title(int p_tab) const { - Control *child = _get_tab(p_tab); + Control *child = get_tab_control(p_tab); ERR_FAIL_COND_V(!child, ""); if (child->has_meta("_tab_name")) { return child->get_meta("_tab_name"); @@ -1041,14 +1036,14 @@ String TabContainer::get_tab_title(int p_tab) const { } void TabContainer::set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon) { - Control *child = _get_tab(p_tab); + Control *child = get_tab_control(p_tab); ERR_FAIL_COND(!child); child->set_meta("_tab_icon", p_icon); update(); } Ref<Texture2D> TabContainer::get_tab_icon(int p_tab) const { - Control *child = _get_tab(p_tab); + Control *child = get_tab_control(p_tab); ERR_FAIL_COND_V(!child, Ref<Texture2D>()); if (child->has_meta("_tab_icon")) { return child->get_meta("_tab_icon"); @@ -1058,14 +1053,14 @@ Ref<Texture2D> TabContainer::get_tab_icon(int p_tab) const { } void TabContainer::set_tab_disabled(int p_tab, bool p_disabled) { - Control *child = _get_tab(p_tab); + Control *child = get_tab_control(p_tab); ERR_FAIL_COND(!child); child->set_meta("_tab_disabled", p_disabled); update(); } bool TabContainer::get_tab_disabled(int p_tab) const { - Control *child = _get_tab(p_tab); + Control *child = get_tab_control(p_tab); ERR_FAIL_COND_V(!child, false); if (child->has_meta("_tab_disabled")) { return child->get_meta("_tab_disabled"); @@ -1075,7 +1070,7 @@ bool TabContainer::get_tab_disabled(int p_tab) const { } void TabContainer::set_tab_hidden(int p_tab, bool p_hidden) { - Control *child = _get_tab(p_tab); + Control *child = get_tab_control(p_tab); ERR_FAIL_COND(!child); child->set_meta("_tab_hidden", p_hidden); update(); @@ -1094,7 +1089,7 @@ void TabContainer::set_tab_hidden(int p_tab, bool p_hidden) { } bool TabContainer::get_tab_hidden(int p_tab) const { - Control *child = _get_tab(p_tab); + Control *child = get_tab_control(p_tab); ERR_FAIL_COND_V(!child, false); if (child->has_meta("_tab_hidden")) { return child->get_meta("_tab_hidden"); @@ -1136,17 +1131,17 @@ Size2 TabContainer::get_minimum_size() const { ms.y = MAX(ms.y, cms.y); } - Ref<StyleBox> tab_unselected = get_theme_stylebox("tab_unselected"); - Ref<StyleBox> tab_selected = get_theme_stylebox("tab_selected"); - Ref<StyleBox> tab_disabled = get_theme_stylebox("tab_disabled"); - Ref<Font> font = get_theme_font("font"); + Ref<StyleBox> tab_unselected = get_theme_stylebox(SNAME("tab_unselected")); + Ref<StyleBox> tab_selected = get_theme_stylebox(SNAME("tab_selected")); + Ref<StyleBox> tab_disabled = get_theme_stylebox(SNAME("tab_disabled")); + Ref<Font> font = get_theme_font(SNAME("font")); if (tabs_visible) { ms.y += MAX(MAX(tab_unselected->get_minimum_size().y, tab_selected->get_minimum_size().y), tab_disabled->get_minimum_size().y); ms.y += _get_top_margin(); } - Ref<StyleBox> sb = get_theme_stylebox("panel"); + Ref<StyleBox> sb = get_theme_stylebox(SNAME("panel")); ms += sb->get_minimum_size(); return ms; @@ -1199,7 +1194,6 @@ bool TabContainer::get_use_hidden_tabs_for_min_size() const { } void TabContainer::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &TabContainer::_gui_input); ClassDB::bind_method(D_METHOD("get_tab_count"), &TabContainer::get_tab_count); ClassDB::bind_method(D_METHOD("set_current_tab", "tab_idx"), &TabContainer::set_current_tab); ClassDB::bind_method(D_METHOD("get_current_tab"), &TabContainer::get_current_tab); @@ -1218,6 +1212,7 @@ void TabContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_tab_icon", "tab_idx"), &TabContainer::get_tab_icon); ClassDB::bind_method(D_METHOD("set_tab_disabled", "tab_idx", "disabled"), &TabContainer::set_tab_disabled); ClassDB::bind_method(D_METHOD("get_tab_disabled", "tab_idx"), &TabContainer::get_tab_disabled); + ClassDB::bind_method(D_METHOD("get_tab_idx_at_point", "point"), &TabContainer::get_tab_idx_at_point); ClassDB::bind_method(D_METHOD("set_popup", "popup"), &TabContainer::set_popup); ClassDB::bind_method(D_METHOD("get_popup"), &TabContainer::get_popup); ClassDB::bind_method(D_METHOD("set_drag_to_rearrange_enabled", "enabled"), &TabContainer::set_drag_to_rearrange_enabled); diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h index 4ed5255729..fe96df25e8 100644 --- a/scene/gui/tab_container.h +++ b/scene/gui/tab_container.h @@ -57,7 +57,6 @@ private: bool menu_hovered = false; int highlight_arrow = -1; TabAlign align = ALIGN_CENTER; - Control *_get_tab(int p_idx) const; int _get_top_margin() const; mutable ObjectID popup_obj_id; bool drag_to_rearrange_enabled = false; @@ -77,7 +76,7 @@ private: protected: void _child_renamed_callback(); - void _gui_input(const Ref<InputEvent> &p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; void _notification(int p_what); virtual void add_child_notify(Node *p_child) override; virtual void move_child_notify(Node *p_child) override; diff --git a/scene/gui/tabs.cpp b/scene/gui/tabs.cpp index 6f1cff9ec8..3ca2d1c1e9 100644 --- a/scene/gui/tabs.cpp +++ b/scene/gui/tabs.cpp @@ -38,9 +38,9 @@ #include "scene/gui/texture_rect.h" Size2 Tabs::get_minimum_size() const { - Ref<StyleBox> tab_unselected = get_theme_stylebox("tab_unselected"); - Ref<StyleBox> tab_selected = get_theme_stylebox("tab_selected"); - Ref<StyleBox> tab_disabled = get_theme_stylebox("tab_disabled"); + 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 y_margin = MAX(MAX(tab_unselected->get_minimum_size().height, tab_selected->get_minimum_size().height), tab_disabled->get_minimum_size().height); @@ -51,7 +51,7 @@ Size2 Tabs::get_minimum_size() const { if (tex.is_valid()) { ms.height = MAX(ms.height, tex->get_size().height); if (tabs[i].text != "") { - ms.width += get_theme_constant("hseparation"); + ms.width += get_theme_constant(SNAME("hseparation")); } } @@ -69,15 +69,15 @@ Size2 Tabs::get_minimum_size() const { if (tabs[i].right_button.is_valid()) { Ref<Texture2D> rb = tabs[i].right_button; Size2 bms = rb->get_size(); - bms.width += get_theme_constant("hseparation"); + bms.width += get_theme_constant(SNAME("hseparation")); ms.width += bms.width; ms.height = MAX(bms.height + tab_unselected->get_minimum_size().height, ms.height); } if (cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && i == current)) { - Ref<Texture2D> cb = get_theme_icon("close"); + Ref<Texture2D> cb = get_theme_icon(SNAME("close")); Size2 bms = cb->get_size(); - bms.width += get_theme_constant("hseparation"); + bms.width += get_theme_constant(SNAME("hseparation")); ms.width += bms.width; ms.height = MAX(bms.height + tab_unselected->get_minimum_size().height, ms.height); } @@ -90,7 +90,7 @@ Size2 Tabs::get_minimum_size() const { return ms; } -void Tabs::_gui_input(const Ref<InputEvent> &p_event) { +void Tabs::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventMouseMotion> mm = p_event; @@ -100,8 +100,8 @@ void Tabs::_gui_input(const Ref<InputEvent> &p_event) { highlight_arrow = -1; if (buttons_visible) { - Ref<Texture2D> incr = get_theme_icon("increment"); - Ref<Texture2D> decr = get_theme_icon("decrement"); + Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); + Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); if (is_layout_rtl()) { if (pos.x < decr->get_width()) { @@ -148,7 +148,7 @@ void Tabs::_gui_input(const Ref<InputEvent> &p_event) { if (rb_pressing && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { if (rb_hover != -1) { //pressed - emit_signal("right_button_pressed", rb_hover); + emit_signal(SNAME("right_button_pressed"), rb_hover); } rb_pressing = false; @@ -158,7 +158,7 @@ void Tabs::_gui_input(const Ref<InputEvent> &p_event) { if (cb_pressing && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { if (cb_hover != -1) { //pressed - emit_signal("tab_closed", cb_hover); + emit_signal(SNAME("tab_closed"), cb_hover); } cb_pressing = false; @@ -170,8 +170,8 @@ void Tabs::_gui_input(const Ref<InputEvent> &p_event) { Point2 pos(mb->get_position().x, mb->get_position().y); if (buttons_visible) { - Ref<Texture2D> incr = get_theme_icon("increment"); - Ref<Texture2D> decr = get_theme_icon("decrement"); + Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); + Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); if (is_layout_rtl()) { if (pos.x < decr->get_width()) { @@ -229,17 +229,17 @@ void Tabs::_gui_input(const Ref<InputEvent> &p_event) { if (found != -1) { set_current_tab(found); - emit_signal("tab_clicked", found); + emit_signal(SNAME("tab_clicked"), found); } } } } void Tabs::_shape(int p_tab) { - Ref<Font> font = get_theme_font("font"); - int font_size = get_theme_font_size("font_size"); + Ref<Font> font = get_theme_font(SNAME("font")); + int font_size = get_theme_font_size(SNAME("font_size")); - tabs.write[p_tab].xl_text = tr(tabs[p_tab].text); + tabs.write[p_tab].xl_text = atr(tabs[p_tab].text); tabs.write[p_tab].text_buf->clear(); if (tabs[p_tab].text_direction == Control::TEXT_DIRECTION_INHERITED) { tabs.write[p_tab].text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); @@ -274,15 +274,15 @@ void Tabs::_notification(int p_what) { _update_cache(); RID ci = get_canvas_item(); - Ref<StyleBox> tab_unselected = get_theme_stylebox("tab_unselected"); - Ref<StyleBox> tab_selected = get_theme_stylebox("tab_selected"); - Ref<StyleBox> tab_disabled = get_theme_stylebox("tab_disabled"); - Color font_selected_color = get_theme_color("font_selected_color"); - Color font_unselected_color = get_theme_color("font_unselected_color"); - Color font_disabled_color = get_theme_color("font_disabled_color"); - Ref<Texture2D> close = get_theme_icon("close"); - Color font_outline_color = get_theme_color("font_outline_color"); - int outline_size = get_theme_constant("outline_size"); + 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")); + Color font_selected_color = get_theme_color(SNAME("font_selected_color")); + Color font_unselected_color = get_theme_color(SNAME("font_unselected_color")); + Color font_disabled_color = get_theme_color(SNAME("font_disabled_color")); + Ref<Texture2D> close = get_theme_icon(SNAME("close")); + Color font_outline_color = get_theme_color(SNAME("font_outline_color")); + int outline_size = get_theme_constant(SNAME("outline_size")); Vector2 size = get_size(); bool rtl = is_layout_rtl(); @@ -306,10 +306,10 @@ void Tabs::_notification(int p_what) { w = 0; } - Ref<Texture2D> incr = get_theme_icon("increment"); - Ref<Texture2D> decr = get_theme_icon("decrement"); - Ref<Texture2D> incr_hl = get_theme_icon("increment_highlight"); - Ref<Texture2D> decr_hl = get_theme_icon("decrement_highlight"); + Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); + Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); + Ref<Texture2D> incr_hl = get_theme_icon(SNAME("increment_highlight")); + Ref<Texture2D> decr_hl = get_theme_icon(SNAME("decrement_highlight")); int limit = get_size().width; int limit_minus_buttons = get_size().width - incr->get_width() - decr->get_width(); @@ -363,7 +363,7 @@ void Tabs::_notification(int p_what) { icon->draw(ci, Point2i(w, sb->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - icon->get_height()) / 2)); } if (tabs[i].text != "") { - w += icon->get_width() + get_theme_constant("hseparation"); + w += icon->get_width() + get_theme_constant(SNAME("hseparation")); } } @@ -384,10 +384,10 @@ void Tabs::_notification(int p_what) { w += tabs[i].size_text; if (tabs[i].right_button.is_valid()) { - Ref<StyleBox> style = get_theme_stylebox("button"); + Ref<StyleBox> style = get_theme_stylebox(SNAME("button")); Ref<Texture2D> rb = tabs[i].right_button; - w += get_theme_constant("hseparation"); + w += get_theme_constant(SNAME("hseparation")); Rect2 rb_rect; rb_rect.size = style->get_minimum_size() + rb->get_size(); @@ -400,7 +400,7 @@ void Tabs::_notification(int p_what) { if (rb_hover == i) { if (rb_pressing) { - get_theme_stylebox("button_pressed")->draw(ci, rb_rect); + get_theme_stylebox(SNAME("button_pressed"))->draw(ci, rb_rect); } else { style->draw(ci, rb_rect); } @@ -416,10 +416,10 @@ void Tabs::_notification(int p_what) { } if (cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && i == current)) { - Ref<StyleBox> style = get_theme_stylebox("button"); + Ref<StyleBox> style = get_theme_stylebox(SNAME("button")); Ref<Texture2D> cb = close; - w += get_theme_constant("hseparation"); + w += get_theme_constant(SNAME("hseparation")); Rect2 cb_rect; cb_rect.size = style->get_minimum_size() + cb->get_size(); @@ -432,7 +432,7 @@ void Tabs::_notification(int p_what) { if (!tabs[i].disabled && cb_hover == i) { if (cb_pressing) { - get_theme_stylebox("button_pressed")->draw(ci, cb_rect); + get_theme_stylebox(SNAME("button_pressed"))->draw(ci, cb_rect); } else { style->draw(ci, cb_rect); } @@ -503,7 +503,7 @@ void Tabs::set_current_tab(int p_current) { _update_cache(); update(); - emit_signal("tab_changed", p_current); + emit_signal(SNAME("tab_changed"), p_current); } int Tabs::get_current_tab() const { @@ -529,7 +529,7 @@ bool Tabs::get_offset_buttons_visible() const { void Tabs::set_tab_title(int p_tab, const String &p_title) { ERR_FAIL_INDEX(p_tab, tabs.size()); tabs.write[p_tab].text = p_title; - tabs.write[p_tab].xl_text = tr(p_title); + tabs.write[p_tab].xl_text = atr(p_title); _shape(p_tab); update(); minimum_size_changed(); @@ -659,7 +659,7 @@ void Tabs::_update_hover() { } if (hover != hover_now) { hover = hover_now; - emit_signal("tab_hovered", hover); + emit_signal(SNAME("tab_hovered"), hover); } if (hover_buttons == -1) { // no hover @@ -669,11 +669,11 @@ void Tabs::_update_hover() { } void Tabs::_update_cache() { - Ref<StyleBox> tab_disabled = get_theme_stylebox("tab_disabled"); - Ref<StyleBox> tab_unselected = get_theme_stylebox("tab_unselected"); - Ref<StyleBox> tab_selected = get_theme_stylebox("tab_selected"); - Ref<Texture2D> incr = get_theme_icon("increment"); - Ref<Texture2D> decr = get_theme_icon("decrement"); + Ref<StyleBox> tab_disabled = get_theme_stylebox(SNAME("tab_disabled")); + Ref<StyleBox> tab_unselected = get_theme_stylebox(SNAME("tab_unselected")); + Ref<StyleBox> tab_selected = get_theme_stylebox(SNAME("tab_selected")); + Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); + Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); int limit_minus_buttons = get_size().width - incr->get_width() - decr->get_width(); int w = 0; @@ -712,12 +712,12 @@ void Tabs::_update_cache() { slen = m_width - (sb->get_margin(SIDE_LEFT) + sb->get_margin(SIDE_RIGHT)); if (tabs[i].icon.is_valid()) { slen -= tabs[i].icon->get_width(); - slen -= get_theme_constant("hseparation"); + slen -= get_theme_constant(SNAME("hseparation")); } if (cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && i == current)) { - Ref<Texture2D> cb = get_theme_icon("close"); + Ref<Texture2D> cb = get_theme_icon(SNAME("close")); slen -= cb->get_width(); - slen -= get_theme_constant("hseparation"); + slen -= get_theme_constant(SNAME("hseparation")); } slen = MAX(slen, 1); lsize = m_width; @@ -742,10 +742,10 @@ void Tabs::_on_mouse_exited() { void Tabs::add_tab(const String &p_str, const Ref<Texture2D> &p_icon) { Tab t; t.text = p_str; - t.xl_text = tr(p_str); + t.xl_text = atr(p_str); t.text_buf.instantiate(); t.text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); - t.text_buf->add_string(t.xl_text, get_theme_font("font"), get_theme_font_size("font_size"), Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); + t.text_buf->add_string(t.xl_text, get_theme_font(SNAME("font")), get_theme_font_size(SNAME("font_size")), Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); t.icon = p_icon; t.disabled = false; t.ofs_cache = 0; @@ -753,7 +753,7 @@ void Tabs::add_tab(const String &p_str, const Ref<Texture2D> &p_icon) { tabs.push_back(t); _update_cache(); - call_deferred("_update_hover"); + call_deferred(SNAME("_update_hover")); update(); minimum_size_changed(); } @@ -762,7 +762,7 @@ void Tabs::clear_tabs() { tabs.clear(); current = 0; previous = 0; - call_deferred("_update_hover"); + call_deferred(SNAME("_update_hover")); update(); } @@ -773,7 +773,7 @@ void Tabs::remove_tab(int p_idx) { current--; } _update_cache(); - call_deferred("_update_hover"); + call_deferred(SNAME("_update_hover")); update(); minimum_size_changed(); @@ -870,7 +870,7 @@ void Tabs::drop_data(const Point2 &p_point, const Variant &p_data) { hover_now = get_tab_count() - 1; } move_tab(tab_from_id, hover_now); - emit_signal("reposition_active_tab_request", hover_now); + emit_signal(SNAME("reposition_active_tab_request"), hover_now); set_current_tab(hover_now); } else if (get_tabs_rearrange_group() != -1) { // drag and drop between Tabs @@ -887,7 +887,7 @@ void Tabs::drop_data(const Point2 &p_point, const Variant &p_data) { tabs.insert(hover_now, moving_tab); from_tabs->remove_tab(tab_from_id); set_current_tab(hover_now); - emit_signal("tab_changed", hover_now); + emit_signal(SNAME("tab_changed"), hover_now); _update_cache(); } } @@ -949,9 +949,9 @@ void Tabs::move_tab(int from, int to) { int Tabs::get_tab_width(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, tabs.size(), 0); - Ref<StyleBox> tab_unselected = get_theme_stylebox("tab_unselected"); - Ref<StyleBox> tab_selected = get_theme_stylebox("tab_selected"); - Ref<StyleBox> tab_disabled = get_theme_stylebox("tab_disabled"); + 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 x = 0; @@ -959,7 +959,7 @@ int Tabs::get_tab_width(int p_idx) const { if (tex.is_valid()) { x += tex->get_width(); if (tabs[p_idx].text != "") { - x += get_theme_constant("hseparation"); + x += get_theme_constant(SNAME("hseparation")); } } @@ -976,13 +976,13 @@ int Tabs::get_tab_width(int p_idx) const { if (tabs[p_idx].right_button.is_valid()) { Ref<Texture2D> rb = tabs[p_idx].right_button; x += rb->get_width(); - x += get_theme_constant("hseparation"); + x += get_theme_constant(SNAME("hseparation")); } if (cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && p_idx == current)) { - Ref<Texture2D> cb = get_theme_icon("close"); + Ref<Texture2D> cb = get_theme_icon(SNAME("close")); x += cb->get_width(); - x += get_theme_constant("hseparation"); + x += get_theme_constant(SNAME("hseparation")); } return x; @@ -993,8 +993,8 @@ void Tabs::_ensure_no_over_offset() { return; } - Ref<Texture2D> incr = get_theme_icon("increment"); - Ref<Texture2D> decr = get_theme_icon("decrement"); + Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); + Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); int limit = get_size().width; int limit_minus_buttons = get_size().width - incr->get_width() - decr->get_width(); @@ -1034,8 +1034,8 @@ void Tabs::ensure_tab_visible(int p_idx) { } int prev_offset = offset; - Ref<Texture2D> incr = get_theme_icon("increment"); - Ref<Texture2D> decr = get_theme_icon("decrement"); + Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); + Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); int limit = get_size().width; int limit_minus_buttons = get_size().width - incr->get_width() - decr->get_width(); @@ -1107,7 +1107,6 @@ bool Tabs::get_select_with_rmb() const { } void Tabs::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &Tabs::_gui_input); ClassDB::bind_method(D_METHOD("_update_hover"), &Tabs::_update_hover); ClassDB::bind_method(D_METHOD("get_tab_count"), &Tabs::get_tab_count); ClassDB::bind_method(D_METHOD("set_current_tab", "tab_idx"), &Tabs::set_current_tab); diff --git a/scene/gui/tabs.h b/scene/gui/tabs.h index 61c9a5d96a..b044453803 100644 --- a/scene/gui/tabs.h +++ b/scene/gui/tabs.h @@ -112,7 +112,7 @@ private: void _shape(int p_tab); protected: - void _gui_input(const Ref<InputEvent> &p_event); + virtual void gui_input(const Ref<InputEvent> &p_event) override; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 370fdd8b88..06dfc31621 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -37,6 +37,7 @@ #include "core/object/script_language.h" #include "core/os/keyboard.h" #include "core/os/os.h" +#include "core/string/string_builder.h" #include "core/string/translation.h" #include "scene/main/window.h" @@ -45,12 +46,6 @@ #include "editor/editor_scale.h" #endif -#define TAB_PIXELS - -inline bool _is_symbol(char32_t c) { - return is_symbol(c); -} - static bool _is_text_char(char32_t c) { return !is_symbol(c); } @@ -63,57 +58,32 @@ static bool _is_char(char32_t c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; } -static bool _is_pair_right_symbol(char32_t c) { - return c == '"' || - c == '\'' || - c == ')' || - c == ']' || - c == '}'; -} - -static bool _is_pair_left_symbol(char32_t c) { - return c == '"' || - c == '\'' || - c == '(' || - c == '[' || - c == '{'; -} - -static bool _is_pair_symbol(char32_t c) { - return _is_pair_left_symbol(c) || _is_pair_right_symbol(c); -} - -static char32_t _get_right_pair_symbol(char32_t c) { - if (c == '"') { - return '"'; - } - if (c == '\'') { - return '\''; - } - if (c == '(') { - return ')'; - } - if (c == '[') { - return ']'; - } - if (c == '{') { - return '}'; - } - return 0; -} - +/////////////////////////////////////////////////////////////////////////////// +/// TEXT /// /////////////////////////////////////////////////////////////////////////////// void TextEdit::Text::set_font(const Ref<Font> &p_font) { + if (font == p_font) { + return; + } font = p_font; + is_dirty = true; } void TextEdit::Text::set_font_size(int p_font_size) { + if (font_size == p_font_size) { + return; + } font_size = p_font_size; + is_dirty = true; } void TextEdit::Text::set_tab_size(int p_tab_size) { + if (tab_size == p_tab_size) { + return; + } tab_size = p_tab_size; + tab_size_dirty = true; } int TextEdit::Text::get_tab_size() const { @@ -121,16 +91,28 @@ int TextEdit::Text::get_tab_size() const { } 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, String p_language) { +void TextEdit::Text::set_direction_and_language(TextServer::Direction p_direction, const String &p_language) { + if (direction == p_direction && language == p_language) { + return; + } direction = p_direction; language = p_language; + is_dirty = true; } void TextEdit::Text::set_draw_control_chars(bool p_draw_control_chars) { + if (draw_control_chars == p_draw_control_chars) { + return; + } draw_control_chars = p_draw_control_chars; + is_dirty = true; } int TextEdit::Text::get_line_width(int p_line, int p_wrap_index) const { @@ -141,10 +123,8 @@ int TextEdit::Text::get_line_width(int p_line, int p_wrap_index) const { return text[p_line].data_buf->get_size().x; } -int TextEdit::Text::get_line_height(int p_line, int p_wrap_index) const { - ERR_FAIL_INDEX_V(p_line, text.size(), 0); - - return text[p_line].data_buf->get_line_size(p_wrap_index).y; +int TextEdit::Text::get_line_height() const { + return line_height; } void TextEdit::Text::set_width(float p_width) { @@ -176,6 +156,36 @@ _FORCE_INLINE_ const String &TextEdit::Text::operator[](int p_line) const { return text[p_line].data; } +void TextEdit::Text::_calculate_line_height() { + int height = 0; + for (int i = 0; i < text.size(); i++) { + // Found another line with the same height...nothing to update. + if (text[i].height == line_height) { + height = line_height; + break; + } + height = MAX(height, text[i].height); + } + line_height = height; +} + +void TextEdit::Text::_calculate_max_line_width() { + int width = 0; + for (int i = 0; i < text.size(); i++) { + if (is_hidden(i)) { + continue; + } + + // Found another line with the same width...nothing to update. + if (text[i].width == max_width) { + width = max_width; + break; + } + width = MAX(width, text[i].width); + } + max_width = width; +} + void TextEdit::Text::invalidate_cache(int p_line, int p_column, const String &p_ime_text, const Vector<Vector2i> &p_bidi_override) { ERR_FAIL_INDEX(p_line, text.size()); @@ -205,23 +215,65 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, const String &p_ tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size); text.write[p_line].data_buf->tab_align(tabs); } + + // Update height. + const int old_height = text.write[p_line].height; + const int wrap_amount = get_line_wrap_amount(p_line); + int height = font->get_height(font_size); + for (int i = 0; i <= wrap_amount; i++) { + height = MAX(height, text[p_line].data_buf->get_line_size(i).y); + } + text.write[p_line].height = height; + + // If this line has shrunk, this may no longer the the tallest line. + if (old_height == line_height && height < line_height) { + _calculate_line_height(); + } else { + line_height = MAX(height, line_height); + } + + // Update width. + const int old_width = text.write[p_line].width; + int width = get_line_width(p_line); + text.write[p_line].width = width; + + // If this line has shrunk, this may no longer the the longest line. + if (old_width == max_width && width < max_width) { + _calculate_max_line_width(); + } else if (!is_hidden(p_line)) { + max_width = MAX(width, max_width); + } } void TextEdit::Text::invalidate_all_lines() { for (int i = 0; i < text.size(); i++) { text.write[i].data_buf->set_width(width); - if (tab_size > 0) { - Vector<float> tabs; - tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size); - text.write[i].data_buf->tab_align(tabs); + if (tab_size_dirty) { + if (tab_size > 0) { + Vector<float> tabs; + tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size); + text.write[i].data_buf->tab_align(tabs); + } + // Tabs have changes, force width update. + text.write[i].width = get_line_width(i); } } + + if (tab_size_dirty) { + _calculate_max_line_width(); + tab_size_dirty = false; + } } void TextEdit::Text::invalidate_all() { + if (!is_dirty) { + return; + } + for (int i = 0; i < text.size(); i++) { invalidate_cache(i); } + is_dirty = false; } void TextEdit::Text::clear() { @@ -229,16 +281,8 @@ void TextEdit::Text::clear() { insert(0, "", Vector<Vector2i>()); } -int TextEdit::Text::get_max_width(bool p_exclude_hidden) const { - // Quite some work, but should be fast enough. - - int max = 0; - for (int i = 0; i < text.size(); i++) { - if (!p_exclude_hidden || !is_hidden(i)) { - max = MAX(max, get_line_width(i)); - } - } - return max; +int TextEdit::Text::get_max_width() const { + return max_width; } void TextEdit::Text::set(int p_line, const String &p_text, const Vector<Vector2i> &p_bidi_override) { @@ -261,7 +305,20 @@ void TextEdit::Text::insert(int p_at, const String &p_text, const Vector<Vector2 } void TextEdit::Text::remove(int p_at) { + int height = text[p_at].height; + int width = text[p_at].width; + text.remove(p_at); + + // If this is the tallest line, we need to get the next tallest. + if (height == line_height) { + _calculate_line_height(); + } + + // If this is the longest line, we need to get the next longest. + if (width == max_width) { + _calculate_max_line_width(); + } } void TextEdit::Text::add_gutter(int p_at) { @@ -288,269 +345,37 @@ void TextEdit::Text::move_gutters(int p_from_line, int p_to_line) { text.write[p_from_line].gutters.resize(gutter_count); } -//////////////////////////////////////////////////////////////////////////////// - -void TextEdit::_update_scrollbars() { - Size2 size = get_size(); - Size2 hmin = h_scroll->get_combined_minimum_size(); - Size2 vmin = v_scroll->get_combined_minimum_size(); - - v_scroll->set_begin(Point2(size.width - vmin.width, cache.style_normal->get_margin(SIDE_TOP))); - v_scroll->set_end(Point2(size.width, size.height - cache.style_normal->get_margin(SIDE_TOP) - cache.style_normal->get_margin(SIDE_BOTTOM))); - - h_scroll->set_begin(Point2(0, size.height - hmin.height)); - h_scroll->set_end(Point2(size.width - vmin.width, size.height)); - - int visible_rows = get_visible_rows(); - int total_rows = get_total_visible_rows(); - if (scroll_past_end_of_file_enabled) { - total_rows += visible_rows - 1; - } - - int visible_width = size.width - cache.style_normal->get_minimum_size().width; - int total_width = text.get_max_width(true) + vmin.x + gutters_width + gutter_padding; - - if (draw_minimap) { - total_width += cache.minimap_width; - } - - updating_scrolls = true; - - if (total_rows > visible_rows) { - v_scroll->show(); - v_scroll->set_max(total_rows + get_visible_rows_offset()); - v_scroll->set_page(visible_rows + get_visible_rows_offset()); - if (smooth_scroll_enabled) { - v_scroll->set_step(0.25); - } else { - v_scroll->set_step(1); - } - set_v_scroll(get_v_scroll()); - - } else { - cursor.line_ofs = 0; - cursor.wrap_ofs = 0; - v_scroll->set_value(0); - v_scroll->hide(); - } - - if (total_width > visible_width && !is_wrap_enabled()) { - h_scroll->show(); - h_scroll->set_max(total_width); - h_scroll->set_page(visible_width); - if (cursor.x_ofs > (total_width - visible_width)) { - cursor.x_ofs = (total_width - visible_width); - } - if (fabs(h_scroll->get_value() - (double)cursor.x_ofs) >= 1) { - h_scroll->set_value(cursor.x_ofs); - } - - } else { - cursor.x_ofs = 0; - h_scroll->set_value(0); - h_scroll->hide(); - } - - updating_scrolls = false; -} - -void TextEdit::_click_selection_held() { - // Warning: is_mouse_button_pressed(MOUSE_BUTTON_LEFT) returns false for double+ clicks, so this doesn't work for MODE_WORD - // and MODE_LINE. However, moving the mouse triggers _gui_input, which calls these functions too, so that's not a huge problem. - // I'm unsure if there's an actual fix that doesn't have a ton of side effects. - if (Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT) && selection.selecting_mode != SelectionMode::SELECTION_MODE_NONE) { - switch (selection.selecting_mode) { - case SelectionMode::SELECTION_MODE_POINTER: { - _update_selection_mode_pointer(); - } break; - case SelectionMode::SELECTION_MODE_WORD: { - _update_selection_mode_word(); - } break; - case SelectionMode::SELECTION_MODE_LINE: { - _update_selection_mode_line(); - } break; - default: { - break; - } - } - } else { - click_select_held->stop(); - } -} - -Point2 TextEdit::_get_local_mouse_pos() const { - Point2 mp = get_local_mouse_position(); - if (is_layout_rtl()) { - mp.x = get_size().width - mp.x; - } - return mp; -} - -void TextEdit::_update_selection_mode_pointer() { - dragging_selection = true; - Point2 mp = _get_local_mouse_pos(); - - int row, col; - _get_mouse_pos(Point2i(mp.x, mp.y), row, col); - - select(selection.selecting_line, selection.selecting_column, row, col); - - cursor_set_line(row, false); - cursor_set_column(col); - update(); - - click_select_held->start(); -} - -void TextEdit::_update_selection_mode_word() { - dragging_selection = true; - Point2 mp = _get_local_mouse_pos(); - - int row, col; - _get_mouse_pos(Point2i(mp.x, mp.y), row, col); - - String line = text[row]; - int cursor_pos = CLAMP(col, 0, line.length()); - int beg = cursor_pos; - int end = beg; - Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text.get_line_data(row)->get_rid()); - for (int i = 0; i < words.size(); i++) { - if (words[i].x < cursor_pos && words[i].y > cursor_pos) { - beg = words[i].x; - end = words[i].y; - break; - } - } - - // Initial selection. - if (!selection.active) { - select(row, beg, row, end); - selection.selecting_column = beg; - selection.selected_word_beg = beg; - selection.selected_word_end = end; - selection.selected_word_origin = beg; - cursor_set_line(selection.to_line, false); - cursor_set_column(selection.to_column); - } else { - if ((col <= selection.selected_word_origin && row == selection.selecting_line) || row < selection.selecting_line) { - selection.selecting_column = selection.selected_word_end; - select(row, beg, selection.selecting_line, selection.selected_word_end); - cursor_set_line(selection.from_line, false); - cursor_set_column(selection.from_column); - } else { - selection.selecting_column = selection.selected_word_beg; - select(selection.selecting_line, selection.selected_word_beg, row, end); - cursor_set_line(selection.to_line, false); - cursor_set_column(selection.to_column); - } - } - - update(); - - click_select_held->start(); -} - -void TextEdit::_update_selection_mode_line() { - dragging_selection = true; - Point2 mp = _get_local_mouse_pos(); - - int row, col; - _get_mouse_pos(Point2i(mp.x, mp.y), row, col); - - col = 0; - if (row < selection.selecting_line) { - // Cursor is above us. - cursor_set_line(row - 1, false); - selection.selecting_column = text[selection.selecting_line].length(); - } else { - // Cursor is below us. - cursor_set_line(row + 1, false); - selection.selecting_column = 0; - col = text[row].length(); - } - cursor_set_column(0); - - select(selection.selecting_line, selection.selecting_column, row, col); - update(); - - click_select_held->start(); -} - -void TextEdit::_update_minimap_click() { - Point2 mp = _get_local_mouse_pos(); - - int xmargin_end = get_size().width - cache.style_normal->get_margin(SIDE_RIGHT); - if (!dragging_minimap && (mp.x < xmargin_end - minimap_width || mp.y > xmargin_end)) { - minimap_clicked = false; - return; - } - minimap_clicked = true; - dragging_minimap = true; - - int row; - _get_minimap_mouse_row(Point2i(mp.x, mp.y), row); - - if (row >= get_first_visible_line() && (row < get_last_full_visible_line() || row >= (text.size() - 1))) { - minimap_scroll_ratio = v_scroll->get_as_ratio(); - minimap_scroll_click_pos = mp.y; - can_drag_minimap = true; - return; - } - - int wi; - int first_line = row - num_lines_from_rows(row, 0, -get_visible_rows() / 2, wi) + 1; - double delta = get_scroll_pos_for_line(first_line, wi) - get_v_scroll(); - if (delta < 0) { - _scroll_up(-delta); - } else { - _scroll_down(delta); - } -} - -void TextEdit::_update_minimap_drag() { - if (!can_drag_minimap) { - return; - } - - int control_height = _get_control_height(); - int scroll_height = v_scroll->get_max() * (minimap_char_size.y + minimap_line_spacing); - if (control_height > scroll_height) { - control_height = scroll_height; - } - - Point2 mp = _get_local_mouse_pos(); - - double diff = (mp.y - minimap_scroll_click_pos) / control_height; - v_scroll->set_as_ratio(minimap_scroll_ratio + diff); -} +/////////////////////////////////////////////////////////////////////////////// +/// TEXT EDIT /// +/////////////////////////////////////////////////////////////////////////////// void TextEdit::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { _update_caches(); - if (cursor_changed_dirty) { - MessageQueue::get_singleton()->push_call(this, "_cursor_changed_emit"); + if (caret_pos_dirty) { + MessageQueue::get_singleton()->push_call(this, "_emit_caret_changed"); } if (text_changed_dirty) { MessageQueue::get_singleton()->push_call(this, "_text_changed_emit"); } - _update_wrap_at(true); + _update_wrap_at_column(true); } break; case NOTIFICATION_RESIZED: { _update_scrollbars(); - _update_wrap_at(); + _update_wrap_at_column(); } break; case NOTIFICATION_VISIBILITY_CHANGED: { if (is_visible()) { - call_deferred("_update_scrollbars"); - call_deferred("_update_wrap_at"); + call_deferred(SNAME("_update_scrollbars")); + call_deferred(SNAME("_update_wrap_at_column")); } } break; case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_TRANSLATION_CHANGED: case NOTIFICATION_THEME_CHANGED: { _update_caches(); - _update_wrap_at(true); + _update_wrap_at_column(true); } break; case NOTIFICATION_WM_WINDOW_FOCUS_IN: { window_has_focus = true; @@ -585,8 +410,8 @@ void TextEdit::_notification(int p_what) { } break; case NOTIFICATION_DRAW: { if (first_draw) { - // Size may not be the final one, so attempts to ensure cursor was visible may have failed. - adjust_viewport_to_cursor(); + // Size may not be the final one, so attempts to ensure caret was visible may have failed. + adjust_viewport_to_caret(); first_draw = false; } @@ -599,61 +424,36 @@ void TextEdit::_notification(int p_what) { Size2 size = get_size(); bool rtl = is_layout_rtl(); - if ((!has_focus() && !menu->has_focus()) || !window_has_focus) { + if ((!has_focus() && !(menu && menu->has_focus())) || !window_has_focus) { draw_caret = false; } - cache.minimap_width = 0; - if (draw_minimap) { - cache.minimap_width = minimap_width; - } - _update_scrollbars(); RID ci = get_canvas_item(); RenderingServer::get_singleton()->canvas_item_set_clip(get_canvas_item(), true); - int xmargin_beg = cache.style_normal->get_margin(SIDE_LEFT) + gutters_width + gutter_padding; + int xmargin_beg = style_normal->get_margin(SIDE_LEFT) + gutters_width + gutter_padding; - int xmargin_end = size.width - cache.style_normal->get_margin(SIDE_RIGHT) - cache.minimap_width; + int xmargin_end = size.width - style_normal->get_margin(SIDE_RIGHT); + if (draw_minimap) { + xmargin_end -= minimap_width; + } // Let's do it easy for now. - cache.style_normal->draw(ci, Rect2(Point2(), size)); - if (readonly) { - cache.style_readonly->draw(ci, Rect2(Point2(), size)); + style_normal->draw(ci, Rect2(Point2(), size)); + if (!editable) { + style_readonly->draw(ci, Rect2(Point2(), size)); draw_caret = false; } if (has_focus()) { - cache.style_focus->draw(ci, Rect2(Point2(), size)); + style_focus->draw(ci, Rect2(Point2(), size)); } - int visible_rows = get_visible_rows() + 1; + int visible_rows = get_visible_line_count() + 1; - Color color = readonly ? cache.font_readonly_color : cache.font_color; + Color color = !editable ? font_readonly_color : font_color; - if (cache.background_color.a > 0.01) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(), get_size()), cache.background_color); - } - - if (line_length_guidelines) { - const int hard_x = xmargin_beg + (int)cache.font->get_char_size('0', 0, cache.font_size).width * line_length_guideline_hard_col - cursor.x_ofs; - if (hard_x > xmargin_beg && hard_x < xmargin_end) { - if (rtl) { - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2(size.width - hard_x, 0), Point2(size.width - hard_x, size.height), cache.line_length_guideline_color); - } else { - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2(hard_x, 0), Point2(hard_x, size.height), cache.line_length_guideline_color); - } - } - - // Draw a "Soft" line length guideline, less visible than the hard line length guideline. - // It's usually set to a lower column compared to the hard line length guideline. - // Only drawn if its column differs from the hard line length guideline. - const int soft_x = xmargin_beg + (int)cache.font->get_char_size('0', 0, cache.font_size).width * line_length_guideline_soft_col - cursor.x_ofs; - if (hard_x != soft_x && soft_x > xmargin_beg && soft_x < xmargin_end) { - if (rtl) { - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2(size.width - soft_x, 0), Point2(size.width - soft_x, size.height), cache.line_length_guideline_color * Color(1, 1, 1, 0.5)); - } else { - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2(soft_x, 0), Point2(soft_x, size.height), cache.line_length_guideline_color * Color(1, 1, 1, 0.5)); - } - } + if (background_color.a > 0.01) { + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(), get_size()), background_color); } int brace_open_match_line = -1; @@ -665,10 +465,10 @@ void TextEdit::_notification(int p_what) { bool brace_close_matching = false; bool brace_close_mismatch = false; - if (brace_matching_enabled && cursor.line >= 0 && cursor.line < text.size() && cursor.column >= 0) { - if (cursor.column < text[cursor.line].length()) { + if (highlight_matching_braces_enabled && caret.line >= 0 && caret.line < text.size() && caret.column >= 0) { + if (caret.column < text[caret.line].length()) { // Check for open. - char32_t c = text[cursor.line][cursor.column]; + char32_t c = text[caret.line][caret.column]; char32_t closec = 0; if (c == '[') { @@ -682,8 +482,8 @@ void TextEdit::_notification(int p_what) { if (closec != 0) { int stack = 1; - for (int i = cursor.line; i < text.size(); i++) { - int from = i == cursor.line ? cursor.column + 1 : 0; + for (int i = caret.line; i < text.size(); i++) { + int from = i == caret.line ? caret.column + 1 : 0; for (int j = from; j < text[i].length(); j++) { char32_t cc = text[i][j]; // Ignore any brackets inside a string. @@ -733,8 +533,8 @@ void TextEdit::_notification(int p_what) { } } - if (cursor.column > 0) { - char32_t c = text[cursor.line][cursor.column - 1]; + if (caret.column > 0) { + char32_t c = text[caret.line][caret.column - 1]; char32_t closec = 0; if (c == ']') { @@ -748,8 +548,8 @@ void TextEdit::_notification(int p_what) { if (closec != 0) { int stack = 1; - for (int i = cursor.line; i >= 0; i--) { - int from = i == cursor.line ? cursor.column - 2 : text[i].length() - 1; + for (int i = caret.line; i >= 0; i--) { + int from = i == caret.line ? caret.column - 2 : text[i].length() - 1; for (int j = from; j >= 0; j--) { char32_t cc = text[i][j]; // Ignore any brackets inside a string. @@ -801,22 +601,20 @@ void TextEdit::_notification(int p_what) { } // Get the highlighted words. - String highlighted_text = get_selection_text(); + String highlighted_text = get_selected_text(); // Check if highlighted words contain only whitespaces (tabs or spaces). bool only_whitespaces_highlighted = highlighted_text.strip_edges() == String(); - int cursor_wrap_index = get_cursor_wrap_index(); - - //FontDrawer drawer(cache.font, Color(1, 1, 1)); + const int caret_wrap_index = get_caret_wrap_index(); int first_visible_line = get_first_visible_line() - 1; int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0); - draw_amount += times_line_wraps(first_visible_line + 1); + draw_amount += get_line_wrap_count(first_visible_line + 1); // minimap if (draw_minimap) { - int minimap_visible_lines = _get_minimap_visible_rows(); + int minimap_visible_lines = get_minimap_visible_lines(); int minimap_line_height = (minimap_char_size.y + minimap_line_spacing); int minimap_tab_size = minimap_char_size.x * text.get_tab_size(); @@ -827,21 +625,32 @@ void TextEdit::_notification(int p_what) { // calculate the first line. int num_lines_before = round((viewport_offset_y) / minimap_line_height); - int wi; int minimap_line = (v_scroll->get_max() <= minimap_visible_lines) ? -1 : first_visible_line; if (minimap_line >= 0) { - minimap_line -= num_lines_from_rows(first_visible_line, 0, -num_lines_before, wi); + minimap_line -= get_next_visible_line_index_offset_from(first_visible_line, 0, -num_lines_before).x; minimap_line -= (minimap_line > 0 && smooth_scroll_enabled ? 1 : 0); } - int minimap_draw_amount = minimap_visible_lines + times_line_wraps(minimap_line + 1); + int minimap_draw_amount = minimap_visible_lines + get_line_wrap_count(minimap_line + 1); + + // Draw the minimap. + + // Add visual feedback when dragging or hovering the the visible area rectangle. + float viewport_alpha; + if (dragging_minimap) { + viewport_alpha = 0.25; + } else if (hovering_minimap) { + viewport_alpha = 0.175; + } else { + viewport_alpha = 0.1; + } - // draw the minimap - Color viewport_color = (cache.background_color.get_v() < 0.5) ? Color(1, 1, 1, 0.1) : Color(0, 0, 0, 0.1); + const Color viewport_color = (background_color.get_v() < 0.5) ? Color(1, 1, 1, viewport_alpha) : Color(0, 0, 0, viewport_alpha); if (rtl) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - cache.minimap_width, viewport_offset_y, cache.minimap_width, viewport_height), viewport_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, viewport_offset_y, minimap_width, viewport_height), viewport_color); } else { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), viewport_offset_y, cache.minimap_width, viewport_height), viewport_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), viewport_offset_y, minimap_width, viewport_height), viewport_color); } + for (int i = 0; i < minimap_draw_amount; i++) { minimap_line++; @@ -849,7 +658,7 @@ void TextEdit::_notification(int p_what) { break; } - while (is_line_hidden(minimap_line)) { + while (_is_line_hidden(minimap_line)) { minimap_line++; if (minimap_line < 0 || minimap_line >= (int)text.size()) { break; @@ -864,13 +673,13 @@ void TextEdit::_notification(int p_what) { Color line_background_color = text.get_line_background_color(minimap_line); line_background_color.a *= 0.6; - Color current_color = cache.font_color; - if (readonly) { - current_color = cache.font_readonly_color; + Color current_color = font_color; + if (!editable) { + current_color = font_readonly_color; } - Vector<String> wrap_rows = get_wrap_rows_text(minimap_line); - int line_wrap_amount = times_line_wraps(minimap_line); + Vector<String> wrap_rows = get_line_wrapped_text(minimap_line); + int line_wrap_amount = get_line_wrap_count(minimap_line); int last_wrap_column = 0; for (int line_wrap_index = 0; line_wrap_index < line_wrap_amount + 1; line_wrap_index++) { @@ -883,7 +692,7 @@ void TextEdit::_notification(int p_what) { const String &str = wrap_rows[line_wrap_index]; int indent_px = line_wrap_index != 0 ? get_indent_level(minimap_line) : 0; - if (indent_px >= wrap_at) { + if (indent_px >= wrap_at_column) { indent_px = 0; } indent_px = minimap_char_size.x * indent_px; @@ -892,17 +701,17 @@ void TextEdit::_notification(int p_what) { last_wrap_column += wrap_rows[line_wrap_index - 1].length(); } - if (minimap_line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) { + if (minimap_line == caret.line && caret_wrap_index == line_wrap_index && highlight_current_line) { if (rtl) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - cache.minimap_width, i * 3, cache.minimap_width, 2), cache.current_line_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, i * 3, minimap_width, 2), current_line_color); } else { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), i * 3, cache.minimap_width, 2), cache.current_line_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), i * 3, minimap_width, 2), current_line_color); } } else if (line_background_color != Color(0, 0, 0, 0)) { if (rtl) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - cache.minimap_width, i * 3, cache.minimap_width, 2), line_background_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, i * 3, minimap_width, 2), line_background_color); } else { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), i * 3, cache.minimap_width, 2), line_background_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), i * 3, minimap_width, 2), line_background_color); } } @@ -910,10 +719,11 @@ void TextEdit::_notification(int p_what) { int characters = 0; int tabs = 0; for (int j = 0; j < str.length(); j++) { - if (color_map.has(last_wrap_column + j)) { - current_color = color_map[last_wrap_column + j].get("color"); - if (readonly) { - current_color.a = cache.font_readonly_color.a; + const Variant *color_data = color_map.getptr(last_wrap_column + j); + if (color_data != nullptr) { + current_color = (color_data->operator Dictionary()).get("color", font_color); + if (!editable) { + current_color.a = font_readonly_color.a; } } color = current_color; @@ -923,7 +733,7 @@ void TextEdit::_notification(int p_what) { } int xpos = indent_px + ((xmargin_end + minimap_char_size.x) + (minimap_char_size.x * j)) + tabs; - bool out_of_bounds = (xpos >= xmargin_end + cache.minimap_width); + bool out_of_bounds = (xpos >= xmargin_end + minimap_width); bool is_whitespace = _is_whitespace(str[j]); if (!is_whitespace) { @@ -974,18 +784,17 @@ void TextEdit::_notification(int p_what) { int top_limit_y = 0; int bottom_limit_y = get_size().height; - if (readonly) { - top_limit_y += cache.style_readonly->get_margin(SIDE_TOP); - bottom_limit_y -= cache.style_readonly->get_margin(SIDE_BOTTOM); + if (!editable) { + top_limit_y += style_readonly->get_margin(SIDE_TOP); + bottom_limit_y -= style_readonly->get_margin(SIDE_BOTTOM); } else { - top_limit_y += cache.style_normal->get_margin(SIDE_TOP); - bottom_limit_y -= cache.style_normal->get_margin(SIDE_BOTTOM); + top_limit_y += style_normal->get_margin(SIDE_TOP); + bottom_limit_y -= style_normal->get_margin(SIDE_BOTTOM); } // draw main text - cursor.visible = false; - const int caret_wrap_index = get_cursor_wrap_index(); - int row_height = get_row_height(); + caret.visible = false; + int row_height = get_line_height(); int line = first_visible_line; for (int i = 0; i < draw_amount; i++) { line++; @@ -994,7 +803,7 @@ void TextEdit::_notification(int p_what) { continue; } - while (is_line_hidden(line)) { + while (_is_line_hidden(line)) { line++; if (line < 0 || line >= (int)text.size()) { break; @@ -1008,12 +817,12 @@ void TextEdit::_notification(int p_what) { Dictionary color_map = _get_line_syntax_highlighting(line); // Ensure we at least use the font color. - Color current_color = readonly ? cache.font_readonly_color : cache.font_color; + Color current_color = !editable ? font_readonly_color : font_color; const Ref<TextParagraph> ldata = text.get_line_data(line); - Vector<String> wrap_rows = get_wrap_rows_text(line); - int line_wrap_amount = times_line_wraps(line); + Vector<String> wrap_rows = get_line_wrapped_text(line); + int line_wrap_amount = get_line_wrap_count(line); for (int line_wrap_index = 0; line_wrap_index <= line_wrap_amount; line_wrap_index++) { if (line_wrap_index != 0) { @@ -1024,21 +833,21 @@ void TextEdit::_notification(int p_what) { } const String &str = wrap_rows[line_wrap_index]; - int char_margin = xmargin_beg - cursor.x_ofs; + int char_margin = xmargin_beg - caret.x_ofs; int ofs_x = 0; int ofs_y = 0; - if (readonly) { - ofs_x = cache.style_readonly->get_offset().x / 2; - ofs_x -= cache.style_normal->get_offset().x / 2; - ofs_y = cache.style_readonly->get_offset().y / 2; + if (!editable) { + ofs_x = style_readonly->get_offset().x / 2; + ofs_x -= style_normal->get_offset().x / 2; + ofs_y = style_readonly->get_offset().y / 2; } else { - ofs_y = cache.style_normal->get_offset().y / 2; + ofs_y = style_normal->get_offset().y / 2; } - ofs_y += i * row_height + cache.line_spacing / 2; - ofs_y -= cursor.wrap_ofs * row_height; - ofs_y -= get_v_scroll_offset() * row_height; + ofs_y += i * row_height + line_spacing / 2; + ofs_y -= caret.wrap_ofs * row_height; + ofs_y -= _get_v_scroll_offset() * row_height; bool clipped = false; if (ofs_y + row_height < top_limit_y) { @@ -1063,30 +872,30 @@ void TextEdit::_notification(int p_what) { if (str.length() == 0) { // Draw line background if empty as we won't loop at all. - if (line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) { + if (line == caret.line && caret_wrap_index == line_wrap_index && highlight_current_line) { if (rtl) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), cache.current_line_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), current_line_color); } else { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), cache.current_line_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), current_line_color); } } // Give visual indication of empty selected line. if (selection.active && line >= selection.from_line && line <= selection.to_line && char_margin >= xmargin_beg) { - int char_w = cache.font->get_char_size(' ', 0, cache.font_size).width; + int char_w = font->get_char_size(' ', 0, 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), cache.selection_color); + 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 { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, char_w, row_height), cache.selection_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, char_w, row_height), selection_color); } } } else { // If it has text, then draw current line marker in the margin, as line number etc will draw over it, draw the rest of line marker later. - if (line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) { + if (line == caret.line && caret_wrap_index == line_wrap_index && highlight_current_line) { if (rtl) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), cache.current_line_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), current_line_color); } else { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), cache.current_line_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), current_line_color); } } } @@ -1094,7 +903,7 @@ void TextEdit::_notification(int p_what) { if (line_wrap_index == 0) { // Only do these if we are on the first wrapped part of a line. - int gutter_offset = cache.style_normal->get_margin(SIDE_LEFT); + int gutter_offset = style_normal->get_margin(SIDE_LEFT); for (int g = 0; g < gutters.size(); g++) { const GutterInfo gutter = gutters[g]; @@ -1111,15 +920,15 @@ void TextEdit::_notification(int p_what) { Ref<TextLine> tl; tl.instantiate(); - tl->add_string(text, cache.font, cache.font_size); + tl->add_string(text, font, font_size); int yofs = ofs_y + (row_height - tl->get_size().y) / 2; - if (cache.outline_size > 0 && cache.outline_color.a > 0) { - tl->draw_outline(ci, Point2(gutter_offset + ofs_x, yofs), cache.outline_size, cache.outline_color); + if (outline_size > 0 && outline_color.a > 0) { + tl->draw_outline(ci, Point2(gutter_offset + ofs_x, yofs), outline_size, outline_color); } tl->draw(ci, Point2(gutter_offset + ofs_x, yofs), get_line_gutter_item_color(line, g)); } break; - case GUTTER_TPYE_ICON: { + case GUTTER_TYPE_ICON: { const Ref<Texture2D> icon = get_line_gutter_icon(line, g); if (icon.is_null()) { break; @@ -1147,7 +956,7 @@ void TextEdit::_notification(int p_what) { icon->draw_rect(ci, gutter_rect, false, get_line_gutter_item_color(line, g)); } break; - case GUTTER_TPYE_CUSTOM: { + case GUTTER_TYPE_CUSTOM: { if (gutter.custom_draw_obj.is_valid()) { Object *cdo = ObjectDB::get_instance(gutter.custom_draw_obj); if (cdo) { @@ -1167,7 +976,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 + cache.font->get_spacing(Font::SPACING_TOP) + cache.font->get_spacing(Font::SPACING_BOTTOM); + float text_height = TS->shaped_text_get_size(rid).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM); if (rtl) { char_margin = size.width - char_margin - TS->shaped_text_get_size(rid).x; @@ -1189,7 +998,7 @@ void TextEdit::_notification(int p_what) { if (rect.position.x + rect.size.x > xmargin_end) { rect.size.x = xmargin_end - rect.position.x; } - draw_rect(rect, cache.selection_color, true); + draw_rect(rect, selection_color, true); } } @@ -1209,8 +1018,8 @@ void TextEdit::_notification(int p_what) { } else if (rect.position.x + rect.size.x > xmargin_end) { rect.size.x = xmargin_end - rect.position.x; } - draw_rect(rect, cache.search_result_color, true); - draw_rect(rect, cache.search_result_border_color, false); + draw_rect(rect, search_result_color, true); + draw_rect(rect, search_result_border_color, false); } search_text_col = _get_column_pos_of_word(search_text, str, search_flags, search_text_col + 1); @@ -1232,18 +1041,18 @@ void TextEdit::_notification(int p_what) { } else if (rect.position.x + rect.size.x > xmargin_end) { rect.size.x = xmargin_end - rect.position.x; } - draw_rect(rect, cache.word_highlighted_color); + draw_rect(rect, word_highlighted_color); } highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, highlighted_text_col + 1); } } - if (!clipped && select_identifiers_enabled && highlighted_word.length() != 0) { // Highlight word - if (_is_char(highlighted_word[0]) || highlighted_word[0] == '.') { - int highlighted_word_col = _get_column_pos_of_word(highlighted_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); + if (!clipped && lookup_symbol_word.length() != 0) { // Highlight word + if (_is_char(lookup_symbol_word[0]) || lookup_symbol_word[0] == '.') { + int highlighted_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); while (highlighted_word_col != -1) { - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_word_col + start, highlighted_word_col + highlighted_word.length() + start); + Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_word_col + start, highlighted_word_col + lookup_symbol_word.length() + start); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1255,12 +1064,12 @@ void TextEdit::_notification(int p_what) { } else if (rect.position.x + rect.size.x > xmargin_end) { rect.size.x = xmargin_end - rect.position.x; } - rect.position.y = TS->shaped_text_get_ascent(rid) + cache.font->get_underline_position(cache.font_size); - rect.size.y = cache.font->get_underline_thickness(cache.font_size); - draw_rect(rect, cache.font_selected_color); + rect.position.y = TS->shaped_text_get_ascent(rid) + font->get_underline_position(font_size); + rect.size.y = font->get_underline_thickness(font_size); + draw_rect(rect, font_selected_color); } - highlighted_word_col = _get_column_pos_of_word(highlighted_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, highlighted_word_col + 1); + highlighted_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, highlighted_word_col + 1); } } } @@ -1273,12 +1082,12 @@ void TextEdit::_notification(int p_what) { ofs_y += ldata->get_line_ascent(line_wrap_index); int char_ofs = 0; - if (cache.outline_size > 0 && cache.outline_color.a > 0) { + if (outline_size > 0 && outline_color.a > 0) { for (int j = 0; j < gl_size; j++) { for (int k = 0; k < glyphs[j].repeat; k++) { if ((char_ofs + char_margin) >= xmargin_beg && (char_ofs + glyphs[j].advance + char_margin) <= xmargin_end) { if (glyphs[j].font_rid != RID()) { - TS->font_draw_glyph_outline(glyphs[j].font_rid, ci, glyphs[j].font_size, cache.outline_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, cache.outline_color); + TS->font_draw_glyph_outline(glyphs[j].font_rid, ci, glyphs[j].font_size, outline_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, outline_color); } } char_ofs += glyphs[j].advance; @@ -1290,10 +1099,11 @@ void TextEdit::_notification(int p_what) { char_ofs = 0; } for (int j = 0; j < gl_size; j++) { - if (color_map.has(glyphs[j].start)) { - current_color = color_map[glyphs[j].start].get("color"); - if (readonly && current_color.a > cache.font_readonly_color.a) { - current_color.a = cache.font_readonly_color.a; + const Variant *color_data = color_map.getptr(glyphs[j].start); + if (color_data != nullptr) { + current_color = (color_data->operator Dictionary()).get("color", font_color); + if (!editable && current_color.a > font_readonly_color.a) { + current_color.a = font_readonly_color.a; } } @@ -1302,39 +1112,39 @@ void TextEdit::_notification(int p_what) { int sel_to = (line < selection.to_line) ? TS->shaped_text_get_range(rid).y : selection.to_column; if (glyphs[j].start >= sel_from && glyphs[j].end <= sel_to && override_selected_font_color) { - current_color = cache.font_selected_color; + current_color = font_selected_color; } } int char_pos = char_ofs + char_margin + ofs_x; if (char_pos >= xmargin_beg) { - if (brace_matching_enabled) { + if (highlight_matching_braces_enabled) { if ((brace_open_match_line == line && brace_open_match_column == glyphs[j].start) || - (cursor.column == glyphs[j].start && cursor.line == line && cursor_wrap_index == line_wrap_index && (brace_open_matching || brace_open_mismatch))) { + (caret.column == glyphs[j].start && caret.line == line && caret_wrap_index == line_wrap_index && (brace_open_matching || brace_open_mismatch))) { if (brace_open_mismatch) { - current_color = cache.brace_mismatch_color; + current_color = brace_mismatch_color; } - Rect2 rect = Rect2(char_pos, ofs_y + cache.font->get_underline_position(cache.font_size), glyphs[j].advance * glyphs[j].repeat, cache.font->get_underline_thickness(cache.font_size)); + 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)); draw_rect(rect, current_color); } if ((brace_close_match_line == line && brace_close_match_column == glyphs[j].start) || - (cursor.column == glyphs[j].start + 1 && cursor.line == line && cursor_wrap_index == line_wrap_index && (brace_close_matching || brace_close_mismatch))) { + (caret.column == glyphs[j].start + 1 && caret.line == line && caret_wrap_index == line_wrap_index && (brace_close_matching || brace_close_mismatch))) { if (brace_close_mismatch) { - current_color = cache.brace_mismatch_color; + current_color = brace_mismatch_color; } - Rect2 rect = Rect2(char_pos, ofs_y + cache.font->get_underline_position(cache.font_size), glyphs[j].advance * glyphs[j].repeat, cache.font->get_underline_thickness(cache.font_size)); + 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)); draw_rect(rect, current_color); } } if (draw_tabs && ((glyphs[j].flags & TextServer::GRAPHEME_IS_TAB) == TextServer::GRAPHEME_IS_TAB)) { - int yofs = (text_height - cache.tab_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index); - cache.tab_icon->draw(ci, Point2(char_pos, ofs_y + yofs), current_color); + int yofs = (text_height - tab_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index); + tab_icon->draw(ci, Point2(char_pos, ofs_y + yofs), current_color); } else if (draw_spaces && ((glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE)) { - int yofs = (text_height - cache.space_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index); - int xofs = (glyphs[j].advance * glyphs[j].repeat - cache.space_icon->get_width()) / 2; - cache.space_icon->draw(ci, Point2(char_pos + xofs, ofs_y + yofs), current_color); + int yofs = (text_height - space_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index); + int xofs = (glyphs[j].advance * glyphs[j].repeat - space_icon->get_width()) / 2; + space_icon->draw(ci, Point2(char_pos + xofs, ofs_y + yofs), current_color); } } @@ -1354,13 +1164,13 @@ void TextEdit::_notification(int p_what) { } // is_line_folded - if (line_wrap_index == line_wrap_amount && line < text.size() - 1 && is_line_hidden(line + 1)) { - int xofs = char_ofs + char_margin + ofs_x + (cache.folded_eol_icon->get_width() / 2); + if (line_wrap_index == line_wrap_amount && line < text.size() - 1 && _is_line_hidden(line + 1)) { + int xofs = char_ofs + char_margin + ofs_x + (folded_eol_icon->get_width() / 2); if (xofs >= xmargin_beg && xofs < xmargin_end) { - int yofs = (text_height - cache.folded_eol_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index); - Color eol_color = cache.code_folding_color; + int yofs = (text_height - folded_eol_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index); + Color eol_color = code_folding_color; eol_color.a = 1; - cache.folded_eol_icon->draw(ci, Point2(xofs, ofs_y + yofs), eol_color); + folded_eol_icon->draw(ci, Point2(xofs, ofs_y + yofs), eol_color); } } @@ -1371,18 +1181,18 @@ void TextEdit::_notification(int p_what) { int caret_width = 1; #endif - if (!clipped && cursor.line == line && line_wrap_index == caret_wrap_index) { - cursor.draw_pos.y = ofs_y + ldata->get_line_descent(line_wrap_index); + if (!clipped && caret.line == line && line_wrap_index == caret_wrap_index) { + caret.draw_pos.y = ofs_y + ldata->get_line_descent(line_wrap_index); if (ime_text.length() == 0) { Rect2 l_caret, t_caret; TextServer::Direction l_dir, t_dir; if (str.length() != 0) { // Get carets. - TS->shaped_text_get_carets(rid, cursor.column, l_caret, l_dir, t_caret, t_dir); + TS->shaped_text_get_carets(rid, caret.column, l_caret, l_dir, t_caret, t_dir); } else { // No carets, add one at the start. - int h = cache.font->get_height(cache.font_size); + int h = font->get_height(font_size); if (rtl) { l_dir = TextServer::DIRECTION_RTL; l_caret = Rect2(Vector2(xmargin_end - char_margin + ofs_x, -h / 2), Size2(caret_width * 4, h)); @@ -1393,20 +1203,20 @@ void TextEdit::_notification(int p_what) { } if ((l_caret != Rect2() && (l_dir == TextServer::DIRECTION_AUTO || l_dir == (TextServer::Direction)input_direction)) || (t_caret == Rect2())) { - cursor.draw_pos.x = char_margin + ofs_x + l_caret.position.x; + caret.draw_pos.x = char_margin + ofs_x + l_caret.position.x; } else { - cursor.draw_pos.x = char_margin + ofs_x + t_caret.position.x; + caret.draw_pos.x = char_margin + ofs_x + t_caret.position.x; } - if (cursor.draw_pos.x >= xmargin_beg && cursor.draw_pos.x < xmargin_end) { - cursor.visible = true; + if (caret.draw_pos.x >= xmargin_beg && caret.draw_pos.x < xmargin_end) { + caret.visible = true; if (draw_caret) { - if (block_caret || insert_mode) { + if (caret_type == CaretType::CARET_TYPE_BLOCK || overtype_mode) { //Block or underline caret, draw trailing carets at full height. - int h = cache.font->get_height(cache.font_size); + int h = font->get_height(font_size); if (t_caret != Rect2()) { - if (insert_mode) { + if (overtype_mode) { t_caret.position.y = TS->shaped_text_get_descent(rid); t_caret.size.y = caret_width; } else { @@ -1414,20 +1224,37 @@ void TextEdit::_notification(int p_what) { t_caret.size.y = h; } t_caret.position += Vector2(char_margin + ofs_x, ofs_y); + draw_rect(t_caret, caret_color, overtype_mode); - draw_rect(t_caret, cache.caret_color, false); + if (l_caret != Rect2() && l_dir != t_dir) { + l_caret.position += Vector2(char_margin + ofs_x, ofs_y); + l_caret.size.x = caret_width; + draw_rect(l_caret, caret_color * Color(1, 1, 1, 0.5)); + } } else { // End of the line. - if (insert_mode) { - l_caret.position.y = TS->shaped_text_get_descent(rid); + if (gl_size > 0) { + // Adjust for actual line dimensions. + if (overtype_mode) { + l_caret.position.y = TS->shaped_text_get_descent(rid); + l_caret.size.y = caret_width; + } else { + l_caret.position.y = -TS->shaped_text_get_ascent(rid); + l_caret.size.y = h; + } + } else if (overtype_mode) { + l_caret.position.y += l_caret.size.y; l_caret.size.y = caret_width; + } + if (l_caret.position.x >= TS->shaped_text_get_size(rid).x) { + l_caret.size.x = font->get_char_size('m', 0, font_size).x; } else { - l_caret.position.y = -TS->shaped_text_get_ascent(rid); - l_caret.size.y = h; + l_caret.size.x = 3 * caret_width; } l_caret.position += Vector2(char_margin + ofs_x, ofs_y); - l_caret.size.x = cache.font->get_char_size('M', 0, cache.font_size).x; - - draw_rect(l_caret, cache.caret_color, false); + if (l_dir == TextServer::DIRECTION_RTL) { + l_caret.position.x -= l_caret.size.x; + } + draw_rect(l_caret, caret_color, overtype_mode); } } else { // Normal caret. @@ -1435,24 +1262,24 @@ void TextEdit::_notification(int p_what) { // Draw extra marker on top of mid caret. Rect2 trect = Rect2(l_caret.position.x - 3 * caret_width, l_caret.position.y, 6 * caret_width, caret_width); trect.position += Vector2(char_margin + ofs_x, ofs_y); - RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, cache.caret_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color); } l_caret.position += Vector2(char_margin + ofs_x, ofs_y); l_caret.size.x = caret_width; - draw_rect(l_caret, cache.caret_color); + draw_rect(l_caret, caret_color); t_caret.position += Vector2(char_margin + ofs_x, ofs_y); t_caret.size.x = caret_width; - draw_rect(t_caret, cache.caret_color); + draw_rect(t_caret, caret_color); } } } } else { { // IME Intermediate text range. - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, cursor.column, cursor.column + ime_text.length()); + Vector<Vector2> sel = TS->shaped_text_get_selection(rid, caret.column, caret.column + ime_text.length()); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, text_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1465,13 +1292,13 @@ void TextEdit::_notification(int p_what) { rect.size.x = xmargin_end - rect.position.x; } rect.size.y = caret_width; - draw_rect(rect, cache.caret_color); - cursor.draw_pos.x = rect.position.x; + draw_rect(rect, caret_color); + caret.draw_pos.x = rect.position.x; } } { // IME caret. - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, cursor.column + ime_selection.x, cursor.column + ime_selection.x + ime_selection.y); + Vector<Vector2> sel = TS->shaped_text_get_selection(rid, caret.column + ime_selection.x, caret.column + ime_selection.x + ime_selection.y); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, text_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1484,8 +1311,8 @@ void TextEdit::_notification(int p_what) { rect.size.x = xmargin_end - rect.position.x; } rect.size.y = caret_width * 3; - draw_rect(rect, cache.caret_color); - cursor.draw_pos.x = rect.position.x; + draw_rect(rect, caret_color); + caret.draw_pos.x = rect.position.x; } } } @@ -1496,7 +1323,7 @@ void TextEdit::_notification(int p_what) { if (has_focus()) { if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id()); - DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + cursor.draw_pos, get_viewport()->get_window_id()); + DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + caret.draw_pos, get_viewport()->get_window_id()); } } } break; @@ -1509,26 +1336,26 @@ void TextEdit::_notification(int p_what) { if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id()); - DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + _get_cursor_pixel_pos(false), get_viewport()->get_window_id()); + DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + get_caret_draw_pos(), get_viewport()->get_window_id()); } if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) { - int cursor_start = -1; - int cursor_end = -1; + int caret_start = -1; + int caret_end = -1; if (!selection.active) { - String full_text = _base_get_text(0, 0, cursor.line, cursor.column); + String full_text = _base_get_text(0, 0, caret.line, caret.column); - cursor_start = full_text.length(); + caret_start = full_text.length(); } else { String pre_text = _base_get_text(0, 0, selection.from_line, selection.from_column); - String post_text = _base_get_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column); + String post_text = get_selected_text(); - cursor_start = pre_text.length(); - cursor_end = cursor_start + post_text.length(); + caret_start = pre_text.length(); + caret_end = caret_start + post_text.length(); } - DisplayServer::get_singleton()->virtual_keyboard_show(get_text(), get_global_rect(), true, -1, cursor_start, cursor_end); + DisplayServer::get_singleton()->virtual_keyboard_show(get_text(), get_global_rect(), true, -1, caret_start, caret_end); } } break; case NOTIFICATION_FOCUS_EXIT: { @@ -1542,7 +1369,7 @@ void TextEdit::_notification(int p_what) { } ime_text = ""; ime_selection = Point2(); - text.invalidate_cache(cursor.line, cursor.column, ime_text); + text.invalidate_cache(caret.line, caret.column, ime_text); if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) { DisplayServer::get_singleton()->virtual_keyboard_hide(); @@ -1554,754 +1381,20 @@ void TextEdit::_notification(int p_what) { ime_selection = DisplayServer::get_singleton()->ime_get_selection(); String t; - if (cursor.column >= 0) { - t = text[cursor.line].substr(0, cursor.column) + ime_text + text[cursor.line].substr(cursor.column, text[cursor.line].length()); + if (caret.column >= 0) { + t = text[caret.line].substr(0, caret.column) + ime_text + text[caret.line].substr(caret.column, text[caret.line].length()); } else { t = ime_text; } - text.invalidate_cache(cursor.line, cursor.column, t, structured_text_parser(st_parser, st_args, t)); + text.invalidate_cache(caret.line, caret.column, t, structured_text_parser(st_parser, st_args, t)); update(); } } break; } } -void TextEdit::_consume_pair_symbol(char32_t ch) { - int cursor_position_to_move = cursor_get_column() + 1; - - char32_t ch_single[2] = { ch, 0 }; - char32_t ch_single_pair[2] = { _get_right_pair_symbol(ch), 0 }; - char32_t ch_pair[3] = { ch, _get_right_pair_symbol(ch), 0 }; - - if (is_selection_active()) { - int new_column, new_line; - - begin_complex_operation(); - _insert_text(get_selection_from_line(), get_selection_from_column(), - ch_single, - &new_line, &new_column); - - int to_col_offset = 0; - if (get_selection_from_line() == get_selection_to_line()) { - to_col_offset = 1; - } - - _insert_text(get_selection_to_line(), - get_selection_to_column() + to_col_offset, - ch_single_pair, - &new_line, &new_column); - end_complex_operation(); - - cursor_set_line(get_selection_to_line()); - cursor_set_column(get_selection_to_column() + to_col_offset); - - deselect(); - update(); - return; - } - - if ((ch == '\'' || ch == '"') && - cursor_get_column() > 0 && _is_text_char(text[cursor.line][cursor_get_column() - 1]) && !_is_pair_right_symbol(text[cursor.line][cursor_get_column()])) { - insert_text_at_cursor(ch_single); - cursor_set_column(cursor_position_to_move); - return; - } - - if (cursor_get_column() < text[cursor.line].length()) { - if (_is_text_char(text[cursor.line][cursor_get_column()])) { - insert_text_at_cursor(ch_single); - cursor_set_column(cursor_position_to_move); - return; - } - if (_is_pair_right_symbol(ch) && - text[cursor.line][cursor_get_column()] == ch) { - cursor_set_column(cursor_position_to_move); - return; - } - } - - String line = text[cursor.line]; - - bool in_single_quote = false; - bool in_double_quote = false; - bool found_comment = false; - - int c = 0; - while (c < line.length()) { - if (line[c] == '\\') { - c++; // Skip quoted anything. - - if (cursor.column == c) { - break; - } - } else if (!in_single_quote && !in_double_quote && line[c] == '#') { - found_comment = true; - break; - } else { - if (line[c] == '\'' && !in_double_quote) { - in_single_quote = !in_single_quote; - } else if (line[c] == '"' && !in_single_quote) { - in_double_quote = !in_double_quote; - } - } - - c++; - - if (cursor.column == c) { - break; - } - } - - // Do not need to duplicate quotes while in comments - if (found_comment) { - insert_text_at_cursor(ch_single); - cursor_set_column(cursor_position_to_move); - - return; - } - - // Disallow inserting duplicated quotes while already in string - if ((in_single_quote || in_double_quote) && (ch == '"' || ch == '\'')) { - insert_text_at_cursor(ch_single); - cursor_set_column(cursor_position_to_move); - - return; - } - - insert_text_at_cursor(ch_pair); - cursor_set_column(cursor_position_to_move); -} - -void TextEdit::_consume_backspace_for_pair_symbol(int prev_line, int prev_column) { - bool remove_right_symbol = false; - - if (cursor.column < text[cursor.line].length() && cursor.column > 0) { - char32_t left_char = text[cursor.line][cursor.column - 1]; - char32_t right_char = text[cursor.line][cursor.column]; - - if (right_char == _get_right_pair_symbol(left_char)) { - remove_right_symbol = true; - } - } - if (remove_right_symbol) { - _remove_text(prev_line, prev_column, cursor.line, cursor.column + 1); - } else { - _remove_text(prev_line, prev_column, cursor.line, cursor.column); - } -} - -void TextEdit::backspace() { - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_backspace")) { - si->call("_backspace"); - return; - } - - if (readonly) { - return; - } - - if (cursor.column == 0 && cursor.line == 0) { - return; - } - - if (is_selection_active()) { - delete_selection(); - return; - } - - int prev_line = cursor.column ? cursor.line : cursor.line - 1; - int prev_column = cursor.column ? (cursor.column - 1) : (text[cursor.line - 1].length()); - - merge_gutters(cursor.line, prev_line); - - if (is_line_hidden(cursor.line)) { - set_line_as_hidden(prev_line, true); - } - - if (auto_brace_completion_enabled && - cursor.column > 0 && - _is_pair_left_symbol(text[cursor.line][cursor.column - 1])) { - _consume_backspace_for_pair_symbol(prev_line, prev_column); - } else { - _remove_text(prev_line, prev_column, cursor.line, cursor.column); - } - - cursor_set_line(prev_line, false, true); - cursor_set_column(prev_column); -} - -void TextEdit::_swap_current_input_direction() { - if (input_direction == TEXT_DIRECTION_LTR) { - input_direction = TEXT_DIRECTION_RTL; - } else { - input_direction = TEXT_DIRECTION_LTR; - } - cursor_set_column(cursor.column); - update(); -} - -void TextEdit::_new_line(bool p_split_current_line, bool p_above) { - if (readonly) { - return; - } - - begin_complex_operation(); - - bool first_line = false; - if (!p_split_current_line) { - if (p_above) { - if (cursor.line > 0) { - cursor_set_line(cursor.line - 1, false); - cursor_set_column(text[cursor.line].length()); - } else { - cursor_set_column(0); - first_line = true; - } - } else { - cursor_set_column(text[cursor.line].length()); - } - } - - insert_text_at_cursor("\n"); - - if (first_line) { - cursor_set_line(0); - } - - end_complex_operation(); -} - -void TextEdit::_move_cursor_left(bool p_select, bool p_move_by_word) { - // Handle selection - if (p_select) { - _pre_shift_selection(); - } else { - deselect(); - } - - if (p_move_by_word) { - int cc = cursor.column; - - if (cc == 0 && cursor.line > 0) { - cursor_set_line(cursor.line - 1); - cursor_set_column(text[cursor.line].length()); - } else { - Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text.get_line_data(cursor.line)->get_rid()); - for (int i = words.size() - 1; i >= 0; i--) { - if (words[i].x < cc) { - cc = words[i].x; - break; - } - } - cursor_set_column(cc); - } - } else { - // If the cursor is at the start of the line, and not on the first line, move it up to the end of the previous line. - if (cursor.column == 0) { - if (cursor.line > 0) { - cursor_set_line(cursor.line - num_lines_from(CLAMP(cursor.line - 1, 0, text.size() - 1), -1)); - cursor_set_column(text[cursor.line].length()); - } - } else { - if (mid_grapheme_caret_enabled) { - cursor_set_column(cursor_get_column() - 1); - } else { - cursor_set_column(TS->shaped_text_prev_grapheme_pos(text.get_line_data(cursor.line)->get_rid(), cursor_get_column())); - } - } - } - - if (p_select) { - _post_shift_selection(); - } -} - -void TextEdit::_move_cursor_right(bool p_select, bool p_move_by_word) { - // Handle selection - if (p_select) { - _pre_shift_selection(); - } else { - deselect(); - } - - if (p_move_by_word) { - int cc = cursor.column; - - if (cc == text[cursor.line].length() && cursor.line < text.size() - 1) { - cursor_set_line(cursor.line + 1); - cursor_set_column(0); - } else { - Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text.get_line_data(cursor.line)->get_rid()); - for (int i = 0; i < words.size(); i++) { - if (words[i].y > cc) { - cc = words[i].y; - break; - } - } - cursor_set_column(cc); - } - } else { - // If we are at the end of the line, move the caret to the next line down. - if (cursor.column == text[cursor.line].length()) { - if (cursor.line < text.size() - 1) { - cursor_set_line(cursor_get_line() + num_lines_from(CLAMP(cursor.line + 1, 0, text.size() - 1), 1), true, false); - cursor_set_column(0); - } - } else { - if (mid_grapheme_caret_enabled) { - cursor_set_column(cursor_get_column() + 1); - } else { - cursor_set_column(TS->shaped_text_next_grapheme_pos(text.get_line_data(cursor.line)->get_rid(), cursor_get_column())); - } - } - } - - if (p_select) { - _post_shift_selection(); - } -} - -void TextEdit::_move_cursor_up(bool p_select) { - if (p_select) { - _pre_shift_selection(); - } else { - deselect(); - } - - int cur_wrap_index = get_cursor_wrap_index(); - if (cur_wrap_index > 0) { - cursor_set_line(cursor.line, true, false, cur_wrap_index - 1); - } else if (cursor.line == 0) { - cursor_set_column(0); - } else { - int new_line = cursor.line - num_lines_from(cursor.line - 1, -1); - if (line_wraps(new_line)) { - cursor_set_line(new_line, true, false, times_line_wraps(new_line)); - } else { - cursor_set_line(new_line, true, false); - } - } - - if (p_select) { - _post_shift_selection(); - } -} - -void TextEdit::_move_cursor_down(bool p_select) { - if (p_select) { - _pre_shift_selection(); - } else { - deselect(); - } - - int cur_wrap_index = get_cursor_wrap_index(); - if (cur_wrap_index < times_line_wraps(cursor.line)) { - cursor_set_line(cursor.line, true, false, cur_wrap_index + 1); - } else if (cursor.line == get_last_unhidden_line()) { - cursor_set_column(text[cursor.line].length()); - } else { - int new_line = cursor.line + num_lines_from(CLAMP(cursor.line + 1, 0, text.size() - 1), 1); - cursor_set_line(new_line, true, false, 0); - } - - if (p_select) { - _post_shift_selection(); - } -} - -void TextEdit::_move_cursor_to_line_start(bool p_select) { - if (p_select) { - _pre_shift_selection(); - } else { - deselect(); - } - - // Move cursor column to start of wrapped row and then to start of text. - Vector<String> rows = get_wrap_rows_text(cursor.line); - int wi = get_cursor_wrap_index(); - int row_start_col = 0; - for (int i = 0; i < wi; i++) { - row_start_col += rows[i].length(); - } - if (cursor.column == row_start_col || wi == 0) { - // Compute whitespace symbols sequence length. - int current_line_whitespace_len = 0; - while (current_line_whitespace_len < text[cursor.line].length()) { - char32_t c = text[cursor.line][current_line_whitespace_len]; - if (c != '\t' && c != ' ') { - break; - } - current_line_whitespace_len++; - } - - if (cursor_get_column() == current_line_whitespace_len) { - cursor_set_column(0); - } else { - cursor_set_column(current_line_whitespace_len); - } - } else { - cursor_set_column(row_start_col); - } - - if (p_select) { - _post_shift_selection(); - } -} - -void TextEdit::_move_cursor_to_line_end(bool p_select) { - if (p_select) { - _pre_shift_selection(); - } else { - deselect(); - } - - // Move cursor column to end of wrapped row and then to end of text. - Vector<String> rows = get_wrap_rows_text(cursor.line); - int wi = get_cursor_wrap_index(); - int row_end_col = -1; - for (int i = 0; i < wi + 1; i++) { - row_end_col += rows[i].length(); - } - if (wi == rows.size() - 1 || cursor.column == row_end_col) { - cursor_set_column(text[cursor.line].length()); - } else { - cursor_set_column(row_end_col); - } - - if (p_select) { - _post_shift_selection(); - } -} - -void TextEdit::_move_cursor_page_up(bool p_select) { - if (p_select) { - _pre_shift_selection(); - } else { - deselect(); - } - - int wi; - int n_line = cursor.line - num_lines_from_rows(cursor.line, get_cursor_wrap_index(), -get_visible_rows(), wi) + 1; - cursor_set_line(n_line, true, false, wi); - - if (p_select) { - _post_shift_selection(); - } -} - -void TextEdit::_move_cursor_page_down(bool p_select) { - if (p_select) { - _pre_shift_selection(); - } else { - deselect(); - } - - int wi; - int n_line = cursor.line + num_lines_from_rows(cursor.line, get_cursor_wrap_index(), get_visible_rows(), wi) - 1; - cursor_set_line(n_line, true, false, wi); - - if (p_select) { - _post_shift_selection(); - } -} - -void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) { - if (readonly) { - return; - } - - if (is_selection_active() || (!p_all_to_left && !p_word)) { - backspace(); - return; - } - - if (p_all_to_left) { - int cursor_current_column = cursor.column; - cursor.column = 0; - _remove_text(cursor.line, 0, cursor.line, cursor_current_column); - return; - } - - if (p_word) { - int line = cursor.line; - int column = cursor.column; - - Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid()); - for (int i = words.size() - 1; i >= 0; i--) { - if (words[i].x < column) { - column = words[i].x; - break; - } - } - - _remove_text(line, column, cursor.line, cursor.column); - - cursor_set_line(line, false); - cursor_set_column(column); - return; - } -} - -void TextEdit::_delete(bool p_word, bool p_all_to_right) { - if (readonly) { - return; - } - - if (is_selection_active()) { - delete_selection(); - return; - } - int curline_len = text[cursor.line].length(); - - if (cursor.line == text.size() - 1 && cursor.column == curline_len) { - return; // Last line, last column: Nothing to do. - } - - int next_line = cursor.column < curline_len ? cursor.line : cursor.line + 1; - int next_column; - - if (p_all_to_right) { - // Delete everything to right of cursor - next_column = curline_len; - next_line = cursor.line; - } else if (p_word && cursor.column < curline_len - 1) { - // Delete next word to right of cursor - int line = cursor.line; - int column = cursor.column; - - Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid()); - for (int i = 0; i < words.size(); i++) { - if (words[i].y > column) { - column = words[i].y; - break; - } - } - - next_line = line; - next_column = column; - } else { - // Delete one character - next_column = cursor.column < curline_len ? (cursor.column + 1) : 0; - if (mid_grapheme_caret_enabled) { - next_column = cursor.column < curline_len ? (cursor.column + 1) : 0; - } else { - next_column = cursor.column < curline_len ? TS->shaped_text_next_grapheme_pos(text.get_line_data(cursor.line)->get_rid(), (cursor.column)) : 0; - } - } - - _remove_text(cursor.line, cursor.column, next_line, next_column); - update(); -} - -void TextEdit::delete_selection() { - if (!is_selection_active()) { - return; - } - - selection.active = false; - _remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column); - cursor_set_line(selection.from_line, false, false); - cursor_set_column(selection.from_column); - update(); -} - -void TextEdit::_move_cursor_document_start(bool p_select) { - if (p_select) { - _pre_shift_selection(); - } else { - deselect(); - } - - cursor_set_line(0); - cursor_set_column(0); - - if (p_select) { - _post_shift_selection(); - } -} - -void TextEdit::_move_cursor_document_end(bool p_select) { - if (p_select) { - _pre_shift_selection(); - } else { - deselect(); - } - - cursor_set_line(get_last_unhidden_line(), true, false, 9999); - cursor_set_column(text[cursor.line].length()); - - if (p_select) { - _post_shift_selection(); - } -} - -void TextEdit::_handle_unicode_character(uint32_t unicode, bool p_had_selection) { - if (p_had_selection) { - delete_selection(); - } - - // Remove the old character if in insert mode and no selection. - if (insert_mode && !p_had_selection) { - begin_complex_operation(); - - // Make sure we don't try and remove empty space. - if (cursor.column < get_line(cursor.line).length()) { - _remove_text(cursor.line, cursor.column, cursor.line, cursor.column + 1); - } - } - - const char32_t chr[2] = { (char32_t)unicode, 0 }; - - if (auto_brace_completion_enabled && _is_pair_symbol(chr[0])) { - _consume_pair_symbol(chr[0]); - } else { - _insert_text_at_cursor(chr); - } - - if ((insert_mode && !p_had_selection) || (selection.active != p_had_selection)) { - end_complex_operation(); - } -} - -void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) const { - float rows = p_mouse.y; - rows -= cache.style_normal->get_margin(SIDE_TOP); - rows /= get_row_height(); - rows += get_v_scroll_offset(); - int first_vis_line = get_first_visible_line(); - int row = first_vis_line + Math::floor(rows); - int wrap_index = 0; - - if (is_wrap_enabled() || is_hiding_enabled()) { - int f_ofs = num_lines_from_rows(first_vis_line, cursor.wrap_ofs, rows + (1 * SGN(rows)), wrap_index) - 1; - if (rows < 0) { - row = first_vis_line - f_ofs; - } else { - row = first_vis_line + f_ofs; - } - } - - if (row < 0) { - row = 0; - } - - int col = 0; - - if (row >= text.size()) { - row = text.size() - 1; - col = text[row].size(); - } else { - int colx = p_mouse.x - (cache.style_normal->get_margin(SIDE_LEFT) + gutters_width + gutter_padding); - colx += cursor.x_ofs; - col = get_char_pos_for_line(colx, row, wrap_index); - if (is_wrap_enabled() && wrap_index < times_line_wraps(row)) { - // Move back one if we are at the end of the row. - Vector<String> rows2 = get_wrap_rows_text(row); - int row_end_col = 0; - for (int i = 0; i < wrap_index + 1; i++) { - row_end_col += rows2[i].length(); - } - if (col >= row_end_col) { - col -= 1; - } - } - - RID text_rid = text.get_line_data(row)->get_line_rid(wrap_index); - if (is_layout_rtl()) { - colx = TS->shaped_text_get_size(text_rid).x - colx; - } - col = TS->shaped_text_hit_test_position(text_rid, colx); - } - - r_row = row; - r_col = col; -} - -Vector2i TextEdit::_get_cursor_pixel_pos(bool p_adjust_viewport) { - if (p_adjust_viewport) { - adjust_viewport_to_cursor(); - } - int row = 1; - for (int i = get_first_visible_line(); i < cursor.line; i++) { - if (!is_line_hidden(i)) { - row += times_line_wraps(i) + 1; - } - } - row += cursor.wrap_ofs; - - // Calculate final pixel position - int y = (row - get_v_scroll_offset()) * get_row_height(); - int x = cache.style_normal->get_margin(SIDE_LEFT) + gutters_width + gutter_padding - cursor.x_ofs; - - Rect2 l_caret, t_caret; - TextServer::Direction l_dir, t_dir; - RID text_rid = text.get_line_data(cursor.line)->get_line_rid(cursor.wrap_ofs); - TS->shaped_text_get_carets(text_rid, cursor.column, l_caret, l_dir, t_caret, t_dir); - if ((l_caret != Rect2() && (l_dir == TextServer::DIRECTION_AUTO || l_dir == (TextServer::Direction)input_direction)) || (t_caret == Rect2())) { - x += l_caret.position.x; - } else { - x += t_caret.position.x; - } - - return Vector2i(x, y); -} - -void TextEdit::_get_minimap_mouse_row(const Point2i &p_mouse, int &r_row) const { - float rows = p_mouse.y; - rows -= cache.style_normal->get_margin(SIDE_TOP); - rows /= (minimap_char_size.y + minimap_line_spacing); - rows += get_v_scroll_offset(); - - // calculate visible lines - int minimap_visible_lines = _get_minimap_visible_rows(); - int visible_rows = get_visible_rows() + 1; - int first_visible_line = get_first_visible_line() - 1; - int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0); - draw_amount += times_line_wraps(first_visible_line + 1); - int minimap_line_height = (minimap_char_size.y + minimap_line_spacing); - - // calculate viewport size and y offset - int viewport_height = (draw_amount - 1) * minimap_line_height; - int control_height = _get_control_height() - viewport_height; - int viewport_offset_y = round(get_scroll_pos_for_line(first_visible_line) * control_height) / ((v_scroll->get_max() <= minimap_visible_lines) ? (minimap_visible_lines - draw_amount) : (v_scroll->get_max() - draw_amount)); - - // calculate the first line. - int num_lines_before = round((viewport_offset_y) / minimap_line_height); - int wi; - int minimap_line = (v_scroll->get_max() <= minimap_visible_lines) ? -1 : first_visible_line; - if (first_visible_line > 0 && minimap_line >= 0) { - minimap_line -= num_lines_from_rows(first_visible_line, 0, -num_lines_before, wi); - minimap_line -= (minimap_line > 0 && smooth_scroll_enabled ? 1 : 0); - } else { - minimap_line = 0; - } - - int row = minimap_line + Math::floor(rows); - int wrap_index = 0; - - if (is_wrap_enabled() || is_hiding_enabled()) { - int f_ofs = num_lines_from_rows(minimap_line, cursor.wrap_ofs, rows + (1 * SGN(rows)), wrap_index) - 1; - if (rows < 0) { - row = minimap_line - f_ofs; - } else { - row = minimap_line + f_ofs; - } - } - - if (row < 0) { - row = 0; - } - - if (row >= text.size()) { - row = text.size() - 1; - } - - r_row = row; -} - -void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { +void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { ERR_FAIL_COND(p_gui_input.is_null()); double prev_v_scroll = v_scroll->get_value(); @@ -2351,17 +1444,18 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { _reset_caret_blink_timer(); - int row, col; - _get_mouse_pos(Point2i(mpos.x, mpos.y), row, col); + Point2i pos = get_line_column_at_pos(Point2i(mpos.x, mpos.y)); + int row = pos.y; + int col = pos.x; - int left_margin = cache.style_normal->get_margin(SIDE_LEFT); + int left_margin = style_normal->get_margin(SIDE_LEFT); for (int i = 0; i < gutters.size(); i++) { if (!gutters[i].draw || gutters[i].width <= 0) { continue; } if (mpos.x > left_margin && mpos.x <= (left_margin + gutters[i].width) - 3) { - emit_signal("gutter_clicked", row, i); + emit_signal(SNAME("gutter_clicked"), row, i); return; } @@ -2376,20 +1470,20 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } } - int prev_col = cursor.column; - int prev_line = cursor.line; + int prev_col = caret.column; + int prev_line = caret.line; - cursor_set_line(row, false, false); - cursor_set_column(col); + set_caret_line(row, false, false); + set_caret_column(col); - if (mb->is_shift_pressed() && (cursor.column != prev_col || cursor.line != prev_line)) { + if (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; selection.from_column = prev_col; selection.from_line = prev_line; - selection.to_column = cursor.column; - selection.to_line = cursor.line; + selection.to_column = caret.column; + selection.to_line = caret.line; if (selection.from_line > selection.to_line || (selection.from_line == selection.to_line && selection.from_column > selection.to_column)) { SWAP(selection.from_column, selection.to_column); @@ -2402,21 +1496,21 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { selection.selecting_column = prev_col; update(); } else { - if (cursor.line < selection.selecting_line || (cursor.line == selection.selecting_line && cursor.column < selection.selecting_column)) { + if (caret.line < selection.selecting_line || (caret.line == selection.selecting_line && caret.column < selection.selecting_column)) { if (selection.shiftclick_left) { selection.shiftclick_left = !selection.shiftclick_left; } - selection.from_column = cursor.column; - selection.from_line = cursor.line; + selection.from_column = caret.column; + selection.from_line = caret.line; - } else if (cursor.line > selection.selecting_line || (cursor.line == selection.selecting_line && cursor.column > selection.selecting_column)) { + } else if (caret.line > selection.selecting_line || (caret.line == selection.selecting_line && caret.column > selection.selecting_column)) { if (!selection.shiftclick_left) { SWAP(selection.from_column, selection.to_column); SWAP(selection.from_line, selection.to_line); selection.shiftclick_left = !selection.shiftclick_left; } - selection.to_column = cursor.column; - selection.to_line = cursor.line; + selection.to_column = caret.column; + selection.to_line = caret.line; } else { selection.active = false; @@ -2431,16 +1525,20 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { selection.selecting_column = col; } - if (!mb->is_double_click() && (OS::get_singleton()->get_ticks_msec() - last_dblclk) < 600 && cursor.line == prev_line) { + const int triple_click_timeout = 600; + const int triple_click_tolerance = 5; + + if (!mb->is_double_click() && (OS::get_singleton()->get_ticks_msec() - last_dblclk) < triple_click_timeout && mb->get_position().distance_to(last_dblclk_pos) < triple_click_tolerance) { // Triple-click select line. selection.selecting_mode = SelectionMode::SELECTION_MODE_LINE; _update_selection_mode_line(); last_dblclk = 0; - } else if (mb->is_double_click() && text[cursor.line].length()) { + } else if (mb->is_double_click() && text[caret.line].length()) { // Double-click select word. selection.selecting_mode = SelectionMode::SELECTION_MODE_WORD; _update_selection_mode_word(); last_dblclk = OS::get_singleton()->get_ticks_msec(); + last_dblclk_pos = mb->get_position(); } update(); @@ -2449,11 +1547,12 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && context_menu_enabled) { _reset_caret_blink_timer(); - int row, col; - _get_mouse_pos(Point2i(mpos.x, mpos.y), row, col); + Point2i pos = get_line_column_at_pos(Point2i(mpos.x, mpos.y)); + int row = pos.y; + int col = pos.x; - if (is_right_click_moving_caret()) { - if (is_selection_active()) { + if (is_move_caret_on_right_click_enabled()) { + if (has_selection()) { int from_line = get_selection_from_line(); int to_line = get_selection_to_line(); int from_column = get_selection_from_column(); @@ -2464,28 +1563,20 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { deselect(); } } - if (!is_selection_active()) { - cursor_set_line(row, true, false); - cursor_set_column(col); + if (!has_selection()) { + set_caret_line(row, true, false); + set_caret_column(col); } } + _generate_context_menu(); menu->set_position(get_screen_transform().xform(mpos)); menu->set_size(Vector2(1, 1)); - _generate_context_menu(); menu->popup(); grab_focus(); } } else { if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { - if (mb->is_command_pressed() && highlighted_word != String()) { - int row, col; - _get_mouse_pos(Point2i(mpos.x, mpos.y), row, col); - - emit_signal("symbol_lookup", highlighted_word, row, col); - return; - } - dragging_minimap = false; dragging_selection = false; can_drag_minimap = false; @@ -2520,18 +1611,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { if (is_layout_rtl()) { mpos.x = get_size().x - mpos.x; } - if (select_identifiers_enabled) { - if (!dragging_minimap && !dragging_selection && mm->is_command_pressed() && mm->get_button_mask() == 0) { - String new_word = get_word_at_pos(mpos); - if (new_word != highlighted_word) { - emit_signal("symbol_validate", new_word); - } - } else { - if (highlighted_word != String()) { - set_highlighted_word(String()); - } - } - } if (mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT && get_viewport()->gui_get_drag_data() == Variant()) { // Ignore if dragging. _reset_caret_blink_timer(); @@ -2559,6 +1638,10 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } } + if (draw_minimap && !dragging_selection) { + _update_minimap_hover(); + } + if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) { accept_event(); // Accept event if scroll changed. } @@ -2566,23 +1649,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { Ref<InputEventKey> k = p_gui_input; if (k.is_valid()) { - // Ctrl + Hover symbols -#ifdef OSX_ENABLED - if (k->get_keycode() == KEY_META) { -#else - if (k->get_keycode() == KEY_CTRL) { -#endif - if (select_identifiers_enabled) { - if (k->is_pressed() && !dragging_minimap && !dragging_selection) { - Point2 mp = _get_local_mouse_pos(); - emit_signal("symbol_validate", get_word_at_pos(mp)); - } else { - set_highlighted_word(String()); - } - } - return; - } - if (!k->is_pressed()) { return; } @@ -2598,9 +1664,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { // * No Modifiers are pressed (except shift) bool allow_unicode_handling = !(k->is_command_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed()); - // Save here for insert mode, just in case it is cleared in the following section. - bool had_selection = selection.active; - selection.selecting_text = false; // Check and handle all built in shortcuts. @@ -2709,9 +1772,10 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { // MISC. if (k->is_action("ui_menu", true)) { if (context_menu_enabled) { - menu->set_position(get_screen_transform().xform(_get_cursor_pixel_pos())); - menu->set_size(Vector2(1, 1)); _generate_context_menu(); + adjust_viewport_to_caret(); + menu->set_position(get_screen_transform().xform(get_caret_draw_pos())); + menu->set_size(Vector2(1, 1)); menu->popup(); menu->grab_focus(); } @@ -2719,7 +1783,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { return; } if (k->is_action("ui_text_toggle_insert_mode", true)) { - set_insert_mode(!insert_mode); + set_overtype_mode_enabled(!overtype_mode); accept_event(); return; } @@ -2729,818 +1793,1752 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { return; } - // CURSOR MOVEMENT + // CARET MOVEMENT k = k->duplicate(); bool shift_pressed = k->is_shift_pressed(); // Remove shift or else actions will not match. Use above variable for selection. k->set_shift_pressed(false); - // CURSOR MOVEMENT - LEFT, RIGHT. + // CARET MOVEMENT - LEFT, RIGHT. if (k->is_action("ui_text_caret_word_left", true)) { - _move_cursor_left(shift_pressed, true); + _move_caret_left(shift_pressed, true); accept_event(); return; } if (k->is_action("ui_text_caret_left", true)) { - _move_cursor_left(shift_pressed, false); + _move_caret_left(shift_pressed, false); accept_event(); return; } if (k->is_action("ui_text_caret_word_right", true)) { - _move_cursor_right(shift_pressed, true); + _move_caret_right(shift_pressed, true); accept_event(); return; } if (k->is_action("ui_text_caret_right", true)) { - _move_cursor_right(shift_pressed, false); + _move_caret_right(shift_pressed, false); accept_event(); return; } - // CURSOR MOVEMENT - UP, DOWN. + // CARET MOVEMENT - UP, DOWN. if (k->is_action("ui_text_caret_up", true)) { - _move_cursor_up(shift_pressed); + _move_caret_up(shift_pressed); accept_event(); return; } if (k->is_action("ui_text_caret_down", true)) { - _move_cursor_down(shift_pressed); + _move_caret_down(shift_pressed); accept_event(); return; } - // CURSOR MOVEMENT - DOCUMENT START/END. + // CARET MOVEMENT - DOCUMENT START/END. if (k->is_action("ui_text_caret_document_start", true)) { // && shift_pressed) { - _move_cursor_document_start(shift_pressed); + _move_caret_document_start(shift_pressed); accept_event(); return; } if (k->is_action("ui_text_caret_document_end", true)) { // && shift_pressed) { - _move_cursor_document_end(shift_pressed); + _move_caret_document_end(shift_pressed); accept_event(); return; } - // CURSOR MOVEMENT - LINE START/END. + // CARET MOVEMENT - LINE START/END. if (k->is_action("ui_text_caret_line_start", true)) { - _move_cursor_to_line_start(shift_pressed); + _move_caret_to_line_start(shift_pressed); accept_event(); return; } if (k->is_action("ui_text_caret_line_end", true)) { - _move_cursor_to_line_end(shift_pressed); + _move_caret_to_line_end(shift_pressed); accept_event(); return; } - // CURSOR MOVEMENT - PAGE UP/DOWN. + // CARET MOVEMENT - PAGE UP/DOWN. if (k->is_action("ui_text_caret_page_up", true)) { - _move_cursor_page_up(shift_pressed); + _move_caret_page_up(shift_pressed); accept_event(); return; } if (k->is_action("ui_text_caret_page_down", true)) { - _move_cursor_page_down(shift_pressed); + _move_caret_page_down(shift_pressed); accept_event(); return; } - if (allow_unicode_handling && !readonly && k->get_unicode() >= 32) { - // Handle Unicode (if no modifiers active). - _handle_unicode_character(k->get_unicode(), had_selection); + // Handle Unicode (if no modifiers active). Tab has a value of 0x09. + if (allow_unicode_handling && editable && (k->get_unicode() >= 32 || k->get_keycode() == KEY_TAB)) { + handle_unicode_input(k->get_unicode()); accept_event(); return; } } } -void TextEdit::_scroll_up(real_t p_delta) { - if (scrolling && smooth_scroll_enabled && SGN(target_v_scroll - v_scroll->get_value()) != SGN(-p_delta)) { - scrolling = false; - minimap_clicked = false; +/* Input actions. */ +void TextEdit::_swap_current_input_direction() { + if (input_direction == TEXT_DIRECTION_LTR) { + input_direction = TEXT_DIRECTION_RTL; + } else { + input_direction = TEXT_DIRECTION_LTR; + } + set_caret_column(caret.column); + update(); +} + +void TextEdit::_new_line(bool p_split_current_line, bool p_above) { + if (!editable) { + return; } - if (scrolling) { - target_v_scroll = (target_v_scroll - p_delta); + begin_complex_operation(); + + bool first_line = false; + if (!p_split_current_line) { + if (p_above) { + if (caret.line > 0) { + set_caret_line(caret.line - 1, false); + set_caret_column(text[caret.line].length()); + } else { + set_caret_column(0); + first_line = true; + } + } else { + set_caret_column(text[caret.line].length()); + } + } + + insert_text_at_caret("\n"); + + if (first_line) { + set_caret_line(0); + } + + end_complex_operation(); +} + +void TextEdit::_move_caret_left(bool p_select, bool p_move_by_word) { + // Handle selection + if (p_select) { + _pre_shift_selection(); + } else if (selection.active && !p_move_by_word) { + // If a selection is active, move caret to start of selection + set_caret_line(selection.from_line); + set_caret_column(selection.from_column); + deselect(); + return; } else { - target_v_scroll = (get_v_scroll() - p_delta); + deselect(); } - if (smooth_scroll_enabled) { - if (target_v_scroll <= 0) { - target_v_scroll = 0; + if (p_move_by_word) { + int cc = caret.column; + + if (cc == 0 && caret.line > 0) { + set_caret_line(caret.line - 1); + set_caret_column(text[caret.line].length()); + } else { + Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid()); + for (int i = words.size() - 1; i >= 0; i--) { + if (words[i].x < cc) { + cc = words[i].x; + break; + } + } + set_caret_column(cc); } - if (Math::abs(target_v_scroll - v_scroll->get_value()) < 1.0) { - v_scroll->set_value(target_v_scroll); + } else { + // If the caret is at the start of the line, and not on the first line, move it up to the end of the previous line. + if (caret.column == 0) { + if (caret.line > 0) { + set_caret_line(caret.line - get_next_visible_line_offset_from(CLAMP(caret.line - 1, 0, text.size() - 1), -1)); + set_caret_column(text[caret.line].length()); + } } else { - scrolling = true; - set_physics_process_internal(true); + if (caret_mid_grapheme_enabled) { + set_caret_column(get_caret_column() - 1); + } else { + set_caret_column(TS->shaped_text_prev_grapheme_pos(text.get_line_data(caret.line)->get_rid(), get_caret_column())); + } } + } + + if (p_select) { + _post_shift_selection(); + } +} + +void TextEdit::_move_caret_right(bool p_select, bool p_move_by_word) { + // Handle selection + if (p_select) { + _pre_shift_selection(); + } else if (selection.active && !p_move_by_word) { + // If a selection is active, move caret to end of selection + set_caret_line(selection.to_line); + set_caret_column(selection.to_column); + deselect(); + return; } else { - set_v_scroll(target_v_scroll); + deselect(); + } + + if (p_move_by_word) { + int cc = caret.column; + + if (cc == text[caret.line].length() && caret.line < text.size() - 1) { + set_caret_line(caret.line + 1); + set_caret_column(0); + } else { + Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid()); + for (int i = 0; i < words.size(); i++) { + if (words[i].y > cc) { + cc = words[i].y; + break; + } + } + set_caret_column(cc); + } + } else { + // If we are at the end of the line, move the caret to the next line down. + if (caret.column == text[caret.line].length()) { + if (caret.line < text.size() - 1) { + set_caret_line(get_caret_line() + get_next_visible_line_offset_from(CLAMP(caret.line + 1, 0, text.size() - 1), 1), true, false); + set_caret_column(0); + } + } else { + if (caret_mid_grapheme_enabled) { + set_caret_column(get_caret_column() + 1); + } else { + set_caret_column(TS->shaped_text_next_grapheme_pos(text.get_line_data(caret.line)->get_rid(), get_caret_column())); + } + } + } + + if (p_select) { + _post_shift_selection(); } } -void TextEdit::_scroll_down(real_t p_delta) { - if (scrolling && smooth_scroll_enabled && SGN(target_v_scroll - v_scroll->get_value()) != SGN(p_delta)) { - scrolling = false; - minimap_clicked = false; +void TextEdit::_move_caret_up(bool p_select) { + if (p_select) { + _pre_shift_selection(); + } else { + deselect(); } - if (scrolling) { - target_v_scroll = (target_v_scroll + p_delta); + int cur_wrap_index = get_caret_wrap_index(); + if (cur_wrap_index > 0) { + set_caret_line(caret.line, true, false, cur_wrap_index - 1); + } else if (caret.line == 0) { + set_caret_column(0); } else { - target_v_scroll = (get_v_scroll() + p_delta); + int new_line = caret.line - get_next_visible_line_offset_from(caret.line - 1, -1); + if (is_line_wrapped(new_line)) { + set_caret_line(new_line, true, false, get_line_wrap_count(new_line)); + } else { + set_caret_line(new_line, true, false); + } } - if (smooth_scroll_enabled) { - int max_v_scroll = round(v_scroll->get_max() - v_scroll->get_page()); - if (target_v_scroll > max_v_scroll) { - target_v_scroll = max_v_scroll; + if (p_select) { + _post_shift_selection(); + } +} + +void TextEdit::_move_caret_down(bool p_select) { + if (p_select) { + _pre_shift_selection(); + } else { + deselect(); + } + + int cur_wrap_index = get_caret_wrap_index(); + if (cur_wrap_index < get_line_wrap_count(caret.line)) { + set_caret_line(caret.line, true, false, cur_wrap_index + 1); + } else if (caret.line == get_last_unhidden_line()) { + set_caret_column(text[caret.line].length()); + } else { + int new_line = caret.line + get_next_visible_line_offset_from(CLAMP(caret.line + 1, 0, text.size() - 1), 1); + set_caret_line(new_line, true, false, 0); + } + + if (p_select) { + _post_shift_selection(); + } +} + +void TextEdit::_move_caret_to_line_start(bool p_select) { + if (p_select) { + _pre_shift_selection(); + } else { + deselect(); + } + + // Move caret column to start of wrapped row and then to start of text. + Vector<String> rows = get_line_wrapped_text(caret.line); + int wi = get_caret_wrap_index(); + int row_start_col = 0; + for (int i = 0; i < wi; i++) { + row_start_col += rows[i].length(); + } + 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++; } - if (Math::abs(target_v_scroll - v_scroll->get_value()) < 1.0) { - v_scroll->set_value(target_v_scroll); + + if (get_caret_column() == current_line_whitespace_len) { + set_caret_column(0); } else { - scrolling = true; - set_physics_process_internal(true); + set_caret_column(current_line_whitespace_len); } } else { - set_v_scroll(target_v_scroll); + set_caret_column(row_start_col); + } + + if (p_select) { + _post_shift_selection(); } } -void TextEdit::_pre_shift_selection() { - if (!selection.active || selection.selecting_mode == SelectionMode::SELECTION_MODE_NONE) { - selection.selecting_line = cursor.line; - selection.selecting_column = cursor.column; - selection.active = true; +void TextEdit::_move_caret_to_line_end(bool p_select) { + if (p_select) { + _pre_shift_selection(); + } else { + deselect(); } - selection.selecting_mode = SelectionMode::SELECTION_MODE_SHIFT; + // Move caret column to end of wrapped row and then to end of text. + Vector<String> rows = get_line_wrapped_text(caret.line); + int wi = get_caret_wrap_index(); + int row_end_col = -1; + for (int i = 0; i < wi + 1; i++) { + row_end_col += rows[i].length(); + } + if (wi == rows.size() - 1 || caret.column == row_end_col) { + set_caret_column(text[caret.line].length()); + } else { + set_caret_column(row_end_col); + } + + if (p_select) { + _post_shift_selection(); + } } -void TextEdit::_post_shift_selection() { - if (selection.active && selection.selecting_mode == SelectionMode::SELECTION_MODE_SHIFT) { - select(selection.selecting_line, selection.selecting_column, cursor.line, cursor.column); - update(); +void TextEdit::_move_caret_page_up(bool p_select) { + if (p_select) { + _pre_shift_selection(); + } else { + deselect(); } - selection.selecting_text = true; -} + Point2i next_line = get_next_visible_line_index_offset_from(caret.line, get_caret_wrap_index(), -get_visible_line_count()); + int n_line = caret.line - next_line.x + 1; + set_caret_line(n_line, true, false, next_line.y); -void TextEdit::_scroll_lines_up() { - scrolling = false; - minimap_clicked = false; + if (p_select) { + _post_shift_selection(); + } +} - // Adjust the vertical scroll. - set_v_scroll(get_v_scroll() - 1); +void TextEdit::_move_caret_page_down(bool p_select) { + if (p_select) { + _pre_shift_selection(); + } else { + deselect(); + } - // Adjust the cursor to viewport. - if (!selection.active) { - int cur_line = cursor.line; - int cur_wrap = get_cursor_wrap_index(); - int last_vis_line = get_last_full_visible_line(); - int last_vis_wrap = get_last_full_visible_line_wrap_index(); + Point2i next_line = get_next_visible_line_index_offset_from(caret.line, get_caret_wrap_index(), get_visible_line_count()); + int n_line = caret.line + next_line.x - 1; + set_caret_line(n_line, true, false, next_line.y); - if (cur_line > last_vis_line || (cur_line == last_vis_line && cur_wrap > last_vis_wrap)) { - cursor_set_line(last_vis_line, false, false, last_vis_wrap); - } + if (p_select) { + _post_shift_selection(); } } -void TextEdit::_scroll_lines_down() { - scrolling = false; - minimap_clicked = false; +void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) { + if (!editable) { + return; + } - // Adjust the vertical scroll. - set_v_scroll(get_v_scroll() + 1); + if (has_selection() || (!p_all_to_left && !p_word)) { + backspace(); + return; + } - // Adjust the cursor to viewport. - if (!selection.active) { - int cur_line = cursor.line; - int cur_wrap = get_cursor_wrap_index(); - int first_vis_line = get_first_visible_line(); - int first_vis_wrap = cursor.wrap_ofs; + if (p_all_to_left) { + int caret_current_column = caret.column; + caret.column = 0; + _remove_text(caret.line, 0, caret.line, caret_current_column); + return; + } - if (cur_line < first_vis_line || (cur_line == first_vis_line && cur_wrap < first_vis_wrap)) { - cursor_set_line(first_vis_line, false, false, first_vis_wrap); + if (p_word) { + int line = caret.line; + int column = caret.column; + + Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid()); + for (int i = words.size() - 1; i >= 0; i--) { + if (words[i].x < column) { + column = words[i].x; + break; + } } + + _remove_text(line, column, caret.line, caret.column); + + set_caret_line(line, false); + set_caret_column(column); + return; } } -/**** TEXT EDIT CORE API ****/ +void TextEdit::_delete(bool p_word, bool p_all_to_right) { + if (!editable) { + return; + } -void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, int &r_end_line, int &r_end_column) { - // Save for undo. - ERR_FAIL_INDEX(p_line, text.size()); - ERR_FAIL_COND(p_char < 0); + if (has_selection()) { + delete_selection(); + return; + } + int curline_len = text[caret.line].length(); - /* STEP 1: Remove \r from source text and separate in substrings. */ + if (caret.line == text.size() - 1 && caret.column == curline_len) { + return; // Last line, last column: Nothing to do. + } - Vector<String> substrings = p_text.replace("\r", "").split("\n"); + int next_line = caret.column < curline_len ? caret.line : caret.line + 1; + int next_column; - // Is this just a new empty line? - bool shift_first_line = p_char == 0 && p_text.replace("\r", "") == "\n"; + if (p_all_to_right) { + // Delete everything to right of caret + next_column = curline_len; + next_line = caret.line; + } else if (p_word && caret.column < curline_len - 1) { + // Delete next word to right of caret + int line = caret.line; + int column = caret.column; - /* STEP 2: Add spaces if the char is greater than the end of the line. */ - while (p_char > text[p_line].length()) { - text.set(p_line, text[p_line] + String::chr(' '), structured_text_parser(st_parser, st_args, text[p_line] + String::chr(' '))); + Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid()); + for (int i = 0; i < words.size(); i++) { + if (words[i].y > column) { + column = words[i].y; + break; + } + } + + next_line = line; + next_column = column; + } else { + // Delete one character + if (caret_mid_grapheme_enabled) { + next_column = caret.column < curline_len ? (caret.column + 1) : 0; + } else { + next_column = caret.column < curline_len ? TS->shaped_text_next_grapheme_pos(text.get_line_data(caret.line)->get_rid(), (caret.column)) : 0; + } } - /* STEP 3: Separate dest string in pre and post text. */ + _remove_text(caret.line, caret.column, next_line, next_column); + update(); +} - String preinsert_text = text[p_line].substr(0, p_char); - String postinsert_text = text[p_line].substr(p_char, text[p_line].size()); +void TextEdit::_move_caret_document_start(bool p_select) { + if (p_select) { + _pre_shift_selection(); + } else { + deselect(); + } - for (int j = 0; j < substrings.size(); j++) { - // Insert the substrings. + set_caret_line(0); + set_caret_column(0); - if (j == 0) { - text.set(p_line, preinsert_text + substrings[j], structured_text_parser(st_parser, st_args, preinsert_text + substrings[j])); - } else { - text.insert(p_line + j, substrings[j], structured_text_parser(st_parser, st_args, substrings[j])); - } + if (p_select) { + _post_shift_selection(); + } +} - if (j == substrings.size() - 1) { - text.set(p_line + j, text[p_line + j] + postinsert_text, structured_text_parser(st_parser, st_args, text[p_line + j] + postinsert_text)); - } +void TextEdit::_move_caret_document_end(bool p_select) { + if (p_select) { + _pre_shift_selection(); + } else { + deselect(); } - if (shift_first_line) { - text.move_gutters(p_line, p_line + 1); - text.set_hidden(p_line + 1, text.is_hidden(p_line)); + set_caret_line(get_last_unhidden_line(), true, false, 9999); + set_caret_column(text[caret.line].length()); - text.set_hidden(p_line, false); + if (p_select) { + _post_shift_selection(); } +} - text.invalidate_cache(p_line); +void TextEdit::_update_caches() { + /* Internal API for CodeEdit. */ + brace_mismatch_color = get_theme_color(SNAME("brace_mismatch_color"), SNAME("CodeEdit")); + code_folding_color = get_theme_color(SNAME("code_folding_color"), SNAME("CodeEdit")); + folded_eol_icon = get_theme_icon(SNAME("folded_eol_icon"), SNAME("CodeEdit")); - r_end_line = p_line + substrings.size() - 1; - r_end_column = text[r_end_line].length() - postinsert_text.length(); + /* Search */ + search_result_color = get_theme_color(SNAME("search_result_color")); + search_result_border_color = get_theme_color(SNAME("search_result_border_color")); - TextServer::Direction dir = TS->shaped_text_get_dominant_direciton_in_range(text.get_line_data(r_end_line)->get_rid(), (r_end_line == p_line) ? cursor.column : 0, r_end_column); - if (dir != TextServer::DIRECTION_AUTO) { - input_direction = (TextDirection)dir; + /* Caret */ + caret_color = get_theme_color(SNAME("caret_color")); + caret_background_color = get_theme_color(SNAME("caret_background_color")); + + /* Selection */ + font_selected_color = get_theme_color(SNAME("font_selected_color")); + selection_color = get_theme_color(SNAME("selection_color")); + + /* Visual. */ + style_normal = get_theme_stylebox(SNAME("normal")); + style_focus = get_theme_stylebox(SNAME("focus")); + style_readonly = get_theme_stylebox(SNAME("read_only")); + + tab_icon = get_theme_icon(SNAME("tab")); + space_icon = get_theme_icon(SNAME("space")); + + font = get_theme_font(SNAME("font")); + font_size = get_theme_font_size(SNAME("font_size")); + font_color = get_theme_color(SNAME("font_color")); + font_readonly_color = get_theme_color(SNAME("font_readonly_color")); + + outline_size = get_theme_constant(SNAME("outline_size")); + outline_color = get_theme_color(SNAME("font_outline_color")); + + line_spacing = get_theme_constant(SNAME("line_spacing")); + + background_color = get_theme_color(SNAME("background_color")); + current_line_color = get_theme_color(SNAME("current_line_color")); + word_highlighted_color = get_theme_color(SNAME("word_highlighted_color")); + + /* Text properties. */ + TextServer::Direction dir; + if (text_direction == Control::TEXT_DIRECTION_INHERITED) { + dir = is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR; + } else { + dir = (TextServer::Direction)text_direction; } + text.set_direction_and_language(dir, (language != "") ? 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); + text.invalidate_all(); - if (!text_changed_dirty && !setting_text) { - if (is_inside_tree()) { - MessageQueue::get_singleton()->push_call(this, "_text_changed_emit"); - } - text_changed_dirty = true; + /* Syntax highlighting. */ + if (syntax_highlighter.is_valid()) { + syntax_highlighter->set_text_edit(this); } - emit_signal("lines_edited_from", p_line, r_end_line); } -String TextEdit::_base_get_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) const { - ERR_FAIL_INDEX_V(p_from_line, text.size(), String()); - ERR_FAIL_INDEX_V(p_from_column, text[p_from_line].length() + 1, String()); - ERR_FAIL_INDEX_V(p_to_line, text.size(), String()); - ERR_FAIL_INDEX_V(p_to_column, text[p_to_line].length() + 1, String()); - ERR_FAIL_COND_V(p_to_line < p_from_line, String()); // 'from > to'. - ERR_FAIL_COND_V(p_to_line == p_from_line && p_to_column < p_from_column, String()); // 'from > to'. +/* General overrides. */ +Size2 TextEdit::get_minimum_size() const { + return style_normal->get_minimum_size(); +} - String ret; +bool TextEdit::is_text_field() const { + return true; +} - for (int i = p_from_line; i <= p_to_line; i++) { - int begin = (i == p_from_line) ? p_from_column : 0; - int end = (i == p_to_line) ? p_to_column : text[i].length(); +Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const { + Point2i pos = get_line_column_at_pos(p_pos); + int row = pos.y; - if (i > p_from_line) { - ret += "\n"; + int left_margin = style_normal->get_margin(SIDE_LEFT); + int gutter = left_margin + gutters_width; + if (p_pos.x < gutter) { + for (int i = 0; i < gutters.size(); i++) { + if (!gutters[i].draw) { + continue; + } + + if (p_pos.x > left_margin && p_pos.x <= (left_margin + gutters[i].width) - 3) { + if (gutters[i].clickable || is_line_gutter_clickable(row, i)) { + return CURSOR_POINTING_HAND; + } + } + left_margin += gutters[i].width; } - ret += text[i].substr(begin, end - begin); + return CURSOR_ARROW; } - return ret; + int xmargin_end = get_size().width - style_normal->get_margin(SIDE_RIGHT); + if (draw_minimap && p_pos.x > xmargin_end - minimap_width && p_pos.x <= xmargin_end) { + return CURSOR_ARROW; + } + return get_default_cursor_shape(); } -void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) { - ERR_FAIL_INDEX(p_from_line, text.size()); - ERR_FAIL_INDEX(p_from_column, text[p_from_line].length() + 1); - ERR_FAIL_INDEX(p_to_line, text.size()); - ERR_FAIL_INDEX(p_to_column, text[p_to_line].length() + 1); - ERR_FAIL_COND(p_to_line < p_from_line); // 'from > to'. - ERR_FAIL_COND(p_to_line == p_from_line && p_to_column < p_from_column); // 'from > to'. +String TextEdit::get_tooltip(const Point2 &p_pos) const { + if (!tooltip_obj) { + return Control::get_tooltip(p_pos); + } + Point2i pos = get_line_column_at_pos(p_pos); + int row = pos.y; + int col = pos.x; - String pre_text = text[p_from_line].substr(0, p_from_column); - String post_text = text[p_to_line].substr(p_to_column, text[p_to_line].length()); + String s = text[row]; + if (s.length() == 0) { + return Control::get_tooltip(p_pos); + } + int beg, end; + if (select_word(s, col, beg, end)) { + String tt = tooltip_obj->call(tooltip_func, s.substr(beg, end - beg), tooltip_ud); - for (int i = p_from_line; i < p_to_line; i++) { - text.remove(p_from_line + 1); + return tt; } - text.set(p_from_line, pre_text + post_text, structured_text_parser(st_parser, st_args, pre_text + post_text)); - //text.set_line_wrap_amount(p_from_line, -1); - text.invalidate_cache(p_from_line); + return Control::get_tooltip(p_pos); +} + +void TextEdit::set_tooltip_request_func(Object *p_obj, const StringName &p_function, const Variant &p_udata) { + tooltip_obj = p_obj; + tooltip_func = p_function; + tooltip_ud = p_udata; +} - if (!text_changed_dirty && !setting_text) { - if (is_inside_tree()) { - MessageQueue::get_singleton()->push_call(this, "_text_changed_emit"); +/* Text */ +// Text properties. +bool TextEdit::has_ime_text() const { + return !ime_text.is_empty(); +} + +void TextEdit::set_editable(const bool p_editable) { + if (editable == p_editable) { + return; + } + + editable = p_editable; + + update(); +} + +bool TextEdit::is_editable() const { + return editable; +} + +void TextEdit::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) { + text_direction = p_text_direction; + if (text_direction != TEXT_DIRECTION_AUTO && text_direction != TEXT_DIRECTION_INHERITED) { + input_direction = text_direction; } - text_changed_dirty = true; + TextServer::Direction dir; + if (text_direction == Control::TEXT_DIRECTION_INHERITED) { + dir = is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR; + } else { + dir = (TextServer::Direction)text_direction; + } + text.set_direction_and_language(dir, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); + text.invalidate_all(); + + if (menu_dir) { + menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_INHERITED), text_direction == TEXT_DIRECTION_INHERITED); + menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_AUTO), text_direction == TEXT_DIRECTION_AUTO); + menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_LTR), text_direction == TEXT_DIRECTION_LTR); + menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_RTL), text_direction == TEXT_DIRECTION_RTL); + } + update(); } - emit_signal("lines_edited_from", p_to_line, p_from_line); } -void TextEdit::_insert_text(int p_line, int p_char, const String &p_text, int *r_end_line, int *r_end_char) { - if (!setting_text && idle_detect->is_inside_tree()) { - idle_detect->start(); +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_all(); + update(); } +} - if (undo_enabled) { - _clear_redo(); +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]; +} - int retline, retchar; - _base_insert_text(p_line, p_char, p_text, retline, retchar); - if (r_end_line) { - *r_end_line = retline; +void TextEdit::clear_opentype_features() { + opentype_features.clear(); + text.set_font_features(opentype_features); + text.invalidate_all(); + update(); +} + +void TextEdit::set_language(const String &p_language) { + if (language != p_language) { + language = p_language; + TextServer::Direction dir; + if (text_direction == Control::TEXT_DIRECTION_INHERITED) { + dir = is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR; + } else { + dir = (TextServer::Direction)text_direction; + } + text.set_direction_and_language(dir, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); + text.invalidate_all(); + update(); } - if (r_end_char) { - *r_end_char = retchar; +} + +String TextEdit::get_language() const { + return language; +} + +void TextEdit::set_structured_text_bidi_override(Control::StructuredTextParser p_parser) { + if (st_parser != p_parser) { + st_parser = p_parser; + for (int i = 0; i < text.size(); i++) { + text.set(i, text[i], structured_text_parser(st_parser, st_args, text[i])); + } + update(); } +} - if (!undo_enabled) { +Control::StructuredTextParser TextEdit::get_structured_text_bidi_override() const { + return st_parser; +} + +void TextEdit::set_structured_text_bidi_override_options(Array p_args) { + st_args = p_args; + for (int i = 0; i < text.size(); i++) { + text.set(i, text[i], structured_text_parser(st_parser, st_args, text[i])); + } + update(); +} + +Array TextEdit::get_structured_text_bidi_override_options() const { + return st_args; +} + +void TextEdit::set_tab_size(const int p_size) { + ERR_FAIL_COND_MSG(p_size <= 0, "Tab size must be greater than 0."); + if (p_size == text.get_tab_size()) { return; } + text.set_tab_size(p_size); + text.invalidate_all_lines(); + update(); +} - /* UNDO!! */ - TextOperation op; - op.type = TextOperation::TYPE_INSERT; - op.from_line = p_line; - op.from_column = p_char; - op.to_line = retline; - op.to_column = retchar; - op.text = p_text; - op.version = ++version; - op.chain_forward = false; - op.chain_backward = false; +int TextEdit::get_tab_size() const { + return text.get_tab_size(); +} - // See if it should just be set as current op. - if (current_op.type != op.type) { - op.prev_version = get_version(); - _push_current_op(); - current_op = op; +// User controls +void TextEdit::set_overtype_mode_enabled(const bool p_enabled) { + overtype_mode = p_enabled; + update(); +} - return; // Set as current op, return. - } - // See if it can be merged. - if (current_op.to_line != p_line || current_op.to_column != p_char) { - op.prev_version = get_version(); - _push_current_op(); - current_op = op; - return; // Set as current op, return. - } - // Merge current op. +bool TextEdit::is_overtype_mode_enabled() const { + return overtype_mode; +} - current_op.text += p_text; - current_op.to_column = retchar; - current_op.to_line = retline; - current_op.version = op.version; +void TextEdit::set_context_menu_enabled(bool p_enable) { + context_menu_enabled = p_enable; } -void TextEdit::_remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) { - if (!setting_text && idle_detect->is_inside_tree()) { - idle_detect->start(); +bool TextEdit::is_context_menu_enabled() const { + return context_menu_enabled; +} + +void TextEdit::set_shortcut_keys_enabled(bool p_enabled) { + shortcut_keys_enabled = p_enabled; +} + +bool TextEdit::is_shortcut_keys_enabled() const { + return shortcut_keys_enabled; +} + +void TextEdit::set_virtual_keyboard_enabled(bool p_enable) { + virtual_keyboard_enabled = p_enable; +} + +bool TextEdit::is_virtual_keyboard_enabled() const { + return virtual_keyboard_enabled; +} + +// Text manipulation +void TextEdit::clear() { + setting_text = true; + _clear(); + setting_text = false; + emit_signal(SNAME("text_set")); +} + +void TextEdit::_clear() { + clear_undo_history(); + text.clear(); + caret.column = 0; + caret.line = 0; + caret.x_ofs = 0; + caret.line_ofs = 0; + caret.wrap_ofs = 0; + caret.last_fit_x = 0; + selection.active = false; +} + +void TextEdit::set_text(const String &p_text) { + setting_text = true; + if (!undo_enabled) { + _clear(); + insert_text_at_caret(p_text); } - String text; if (undo_enabled) { - _clear_redo(); - text = _base_get_text(p_from_line, p_from_column, p_to_line, p_to_column); + set_caret_line(0); + set_caret_column(0); + + begin_complex_operation(); + deselect(); + _remove_text(0, 0, MAX(0, get_line_count() - 1), MAX(get_line(MAX(get_line_count() - 1, 0)).size() - 1, 0)); + insert_text_at_caret(p_text); + end_complex_operation(); } - _base_remove_text(p_from_line, p_from_column, p_to_line, p_to_column); + set_caret_line(0); + set_caret_column(0); - if (!undo_enabled) { - return; + update(); + setting_text = false; + emit_signal(SNAME("text_set")); +} + +String TextEdit::get_text() const { + StringBuilder ret_text; + const int text_size = text.size(); + for (int i = 0; i < text_size; i++) { + ret_text += text[i]; + if (i != text_size - 1) { + ret_text += "\n"; + } } + return ret_text.as_string(); +} - /* UNDO! */ - TextOperation op; - op.type = TextOperation::TYPE_REMOVE; - op.from_line = p_from_line; - op.from_column = p_from_column; - op.to_line = p_to_line; - op.to_column = p_to_column; - op.text = text; - op.version = ++version; - op.chain_forward = false; - op.chain_backward = false; +int TextEdit::get_line_count() const { + return text.size(); +} - // See if it should just be set as current op. - if (current_op.type != op.type) { - op.prev_version = get_version(); - _push_current_op(); - current_op = op; - return; // Set as current op, return. +void TextEdit::set_line(int p_line, const String &p_new_text) { + if (p_line < 0 || p_line >= text.size()) { + return; } - // See if it can be merged. - if (current_op.from_line == p_to_line && current_op.from_column == p_to_column) { - // Backspace or similar. - current_op.text = text + current_op.text; - current_op.from_line = p_from_line; - current_op.from_column = p_from_column; - return; // Update current op. + _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 = MIN(caret.column, p_new_text.length()); } + if (has_selection() && p_line == selection.to_line && selection.to_column > text[p_line].length()) { + selection.to_column = text[p_line].length(); + } +} - op.prev_version = get_version(); - _push_current_op(); - current_op = op; +String TextEdit::get_line(int p_line) const { + if (p_line < 0 || p_line >= text.size()) { + return ""; + } + return text[p_line]; } -void TextEdit::_insert_text_at_cursor(const String &p_text) { - int new_column, new_line; - _insert_text(cursor.line, cursor.column, p_text, &new_line, &new_column); - _update_scrollbars(); - cursor_set_line(new_line, false); - cursor_set_column(new_column); +int TextEdit::get_line_width(int p_line, int p_wrap_index) const { + ERR_FAIL_INDEX_V(p_line, text.size(), 0); + ERR_FAIL_COND_V(p_wrap_index > get_line_wrap_count(p_line), 0); - update(); + return text.get_line_width(p_line, p_wrap_index); } -int TextEdit::get_char_count() { - int totalsize = 0; +int TextEdit::get_line_height() const { + return text.get_line_height() + line_spacing; +} - for (int i = 0; i < text.size(); i++) { - if (i > 0) { - totalsize++; // Include \n. +int TextEdit::get_indent_level(int p_line) const { + ERR_FAIL_INDEX_V(p_line, text.size(), 0); + + int tab_count = 0; + int whitespace_count = 0; + int line_length = text[p_line].size(); + for (int i = 0; i < line_length - 1; i++) { + if (text[p_line][i] == '\t') { + tab_count++; + } else if (text[p_line][i] == ' ') { + whitespace_count++; + } else { + break; } - totalsize += text[i].length(); } + return tab_count * text.get_tab_size() + whitespace_count; +} - return totalsize; // Omit last \n. +int TextEdit::get_first_non_whitespace_column(int p_line) const { + ERR_FAIL_INDEX_V(p_line, text.size(), 0); + + int col = 0; + while (col < text[p_line].length() && _is_whitespace(text[p_line][col])) { + col++; + } + return col; } -Size2 TextEdit::get_minimum_size() const { - return cache.style_normal->get_minimum_size(); +void TextEdit::swap_lines(int p_from_line, int p_to_line) { + ERR_FAIL_INDEX(p_from_line, text.size()); + ERR_FAIL_INDEX(p_to_line, text.size()); + + String tmp = get_line(p_from_line); + String tmp2 = get_line(p_to_line); + set_line(p_to_line, tmp); + set_line(p_from_line, tmp2); } -int TextEdit::_get_control_height() const { - int control_height = get_size().height; - control_height -= cache.style_normal->get_minimum_size().height; - if (h_scroll->is_visible_in_tree()) { - control_height -= h_scroll->get_size().height; +void TextEdit::insert_line_at(int p_at, const String &p_text) { + ERR_FAIL_INDEX(p_at, text.size()); + + _insert_text(p_at, 0, p_text + "\n"); + if (caret.line >= p_at) { + // offset caret when located after inserted line + ++caret.line; + } + if (has_selection()) { + if (selection.from_line >= p_at) { + // offset selection when located after inserted line + ++selection.from_line; + ++selection.to_line; + } else if (selection.to_line >= p_at) { + // extend selection that includes inserted line + ++selection.to_line; + } } - return control_height; } -int TextEdit::_get_menu_action_accelerator(const String &p_action) { - const List<Ref<InputEvent>> *events = InputMap::get_singleton()->action_get_events(p_action); - if (!events) { - return 0; +void TextEdit::insert_text_at_caret(const String &p_text) { + begin_complex_operation(); + + delete_selection(); + + int new_column, new_line; + _insert_text(caret.line, caret.column, p_text, &new_line, &new_column); + _update_scrollbars(); + + set_caret_line(new_line, false); + set_caret_column(new_column); + update(); + + end_complex_operation(); +} + +void TextEdit::remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) { + ERR_FAIL_INDEX(p_from_line, text.size()); + ERR_FAIL_INDEX(p_from_column, text[p_from_line].length() + 1); + ERR_FAIL_INDEX(p_to_line, text.size()); + ERR_FAIL_INDEX(p_to_column, text[p_to_line].length() + 1); + ERR_FAIL_COND(p_to_line < p_from_line); + ERR_FAIL_COND(p_to_line == p_from_line && p_to_column < p_from_column); + + _remove_text(p_from_line, p_from_column, p_to_line, p_to_column); +} + +int TextEdit::get_last_unhidden_line() const { + // Returns the last line in the text that is not hidden. + if (!_is_hiding_enabled()) { + return text.size() - 1; } - // Use first event in the list for the accelerator. - const List<Ref<InputEvent>>::Element *first_event = events->front(); - if (!first_event) { - return 0; + int last_line; + for (last_line = text.size() - 1; last_line > 0; last_line--) { + if (!_is_line_hidden(last_line)) { + break; + } } + return last_line; +} - const Ref<InputEventKey> event = first_event->get(); - if (event.is_null()) { - return 0; +int TextEdit::get_next_visible_line_offset_from(int p_line_from, int p_visible_amount) const { + // Returns the number of lines (hidden and unhidden) from p_line_from to (p_line_from + visible_amount of unhidden lines). + ERR_FAIL_INDEX_V(p_line_from, text.size(), ABS(p_visible_amount)); + + if (!_is_hiding_enabled()) { + return ABS(p_visible_amount); } - // Use physical keycode if non-zero - if (event->get_physical_keycode() != 0) { - return event->get_physical_keycode_with_modifiers(); + int num_visible = 0; + int num_total = 0; + if (p_visible_amount >= 0) { + for (int i = p_line_from; i < text.size(); i++) { + num_total++; + if (!_is_line_hidden(i)) { + num_visible++; + } + if (num_visible >= p_visible_amount) { + break; + } + } } else { - return event->get_keycode_with_modifiers(); + p_visible_amount = ABS(p_visible_amount); + for (int i = p_line_from; i >= 0; i--) { + num_total++; + if (!_is_line_hidden(i)) { + num_visible++; + } + if (num_visible >= p_visible_amount) { + break; + } + } } + return num_total; } -void TextEdit::_generate_context_menu() { - // Reorganize context menu. - menu->clear(); - if (!readonly) { - menu->add_item(RTR("Cut"), MENU_CUT, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_cut") : 0); - } - menu->add_item(RTR("Copy"), MENU_COPY, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_copy") : 0); - if (!readonly) { - menu->add_item(RTR("Paste"), MENU_PASTE, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_paste") : 0); +Point2i TextEdit::get_next_visible_line_index_offset_from(int p_line_from, int p_wrap_index_from, int p_visible_amount) const { + // Returns the number of lines (hidden and unhidden) from (p_line_from + p_wrap_index_from) row to (p_line_from + visible_amount of unhidden and wrapped rows). + // Wrap index is set to the wrap index of the last line. + int wrap_index = 0; + ERR_FAIL_INDEX_V(p_line_from, text.size(), Point2i(ABS(p_visible_amount), 0)); + + if (!_is_hiding_enabled() && get_line_wrapping_mode() == LineWrappingMode::LINE_WRAPPING_NONE) { + return Point2i(ABS(p_visible_amount), 0); } - menu->add_separator(); - if (is_selecting_enabled()) { - menu->add_item(RTR("Select All"), MENU_SELECT_ALL, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_text_select_all") : 0); + + int num_visible = 0; + int num_total = 0; + if (p_visible_amount == 0) { + num_total = 0; + wrap_index = 0; + } else if (p_visible_amount > 0) { + int i; + num_visible -= p_wrap_index_from; + for (i = p_line_from; i < text.size(); i++) { + num_total++; + if (!_is_line_hidden(i)) { + num_visible++; + num_visible += get_line_wrap_count(i); + } + if (num_visible >= p_visible_amount) { + break; + } + } + wrap_index = get_line_wrap_count(MIN(i, text.size() - 1)) - MAX(0, num_visible - p_visible_amount); + } else { + p_visible_amount = ABS(p_visible_amount); + int i; + num_visible -= get_line_wrap_count(p_line_from) - p_wrap_index_from; + for (i = p_line_from; i >= 0; i--) { + num_total++; + if (!_is_line_hidden(i)) { + num_visible++; + num_visible += get_line_wrap_count(i); + } + if (num_visible >= p_visible_amount) { + break; + } + } + wrap_index = MAX(0, num_visible - p_visible_amount); } - if (!readonly) { - menu->add_item(RTR("Clear"), MENU_CLEAR); - menu->add_separator(); - menu->add_item(RTR("Undo"), MENU_UNDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_undo") : 0); - menu->add_item(RTR("Redo"), MENU_REDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_redo") : 0); + wrap_index = MAX(wrap_index, 0); + return Point2i(num_total, wrap_index); +} + +// Overridable actions +void TextEdit::handle_unicode_input(const uint32_t p_unicode) { + if (GDVIRTUAL_CALL(_handle_unicode_input, p_unicode)) { + return; } - menu->add_separator(); - menu->add_submenu_item(RTR("Text writing direction"), "DirMenu"); - menu->add_separator(); - menu->add_check_item(RTR("Display control characters"), MENU_DISPLAY_UCC); - if (!readonly) { - menu->add_submenu_item(RTR("Insert control character"), "CTLMenu"); + _handle_unicode_input_internal(p_unicode); +} + +void TextEdit::backspace() { + if (GDVIRTUAL_CALL(_backspace)) { + return; } + _backspace_internal(); } -int TextEdit::get_visible_rows() const { - return _get_control_height() / get_row_height(); +void TextEdit::cut() { + if (GDVIRTUAL_CALL(_cut)) { + return; + } + _cut_internal(); } -int TextEdit::_get_minimap_visible_rows() const { - return _get_control_height() / (minimap_char_size.y + minimap_line_spacing); +void TextEdit::copy() { + if (GDVIRTUAL_CALL(_copy)) { + return; + } + _copy_internal(); } -int TextEdit::get_total_visible_rows() const { - // Returns the total amount of rows we need in the editor. - // This skips hidden lines and counts each wrapping of a line. - if (!is_hiding_enabled() && !is_wrap_enabled()) { - return text.size(); +void TextEdit::paste() { + if (GDVIRTUAL_CALL(_paste)) { + return; } + _paste_internal(); +} - int total_rows = 0; - for (int i = 0; i < text.size(); i++) { - if (!text.is_hidden(i)) { - total_rows++; - total_rows += times_line_wraps(i); +// Context menu. +PopupMenu *TextEdit::get_menu() const { + const_cast<TextEdit *>(this)->_generate_context_menu(); + return menu; +} + +bool TextEdit::is_menu_visible() const { + return menu && menu->is_visible(); +} + +void TextEdit::menu_option(int p_option) { + switch (p_option) { + case MENU_CUT: { + cut(); + } break; + case MENU_COPY: { + copy(); + } break; + case MENU_PASTE: { + paste(); + } break; + case MENU_CLEAR: { + if (editable) { + clear(); + } + } break; + case MENU_SELECT_ALL: { + select_all(); + } break; + case MENU_UNDO: { + undo(); + } break; + case MENU_REDO: { + redo(); + } break; + case MENU_DIR_INHERITED: { + set_text_direction(TEXT_DIRECTION_INHERITED); + } break; + case MENU_DIR_AUTO: { + set_text_direction(TEXT_DIRECTION_AUTO); + } break; + case MENU_DIR_LTR: { + set_text_direction(TEXT_DIRECTION_LTR); + } break; + case MENU_DIR_RTL: { + set_text_direction(TEXT_DIRECTION_RTL); + } break; + case MENU_DISPLAY_UCC: { + set_draw_control_chars(!get_draw_control_chars()); + } break; + case MENU_INSERT_LRM: { + if (editable) { + insert_text_at_caret(String::chr(0x200E)); + } + } break; + case MENU_INSERT_RLM: { + if (editable) { + insert_text_at_caret(String::chr(0x200F)); + } + } break; + case MENU_INSERT_LRE: { + if (editable) { + insert_text_at_caret(String::chr(0x202A)); + } + } break; + case MENU_INSERT_RLE: { + if (editable) { + insert_text_at_caret(String::chr(0x202B)); + } + } break; + case MENU_INSERT_LRO: { + if (editable) { + insert_text_at_caret(String::chr(0x202D)); + } + } break; + case MENU_INSERT_RLO: { + if (editable) { + insert_text_at_caret(String::chr(0x202E)); + } + } break; + case MENU_INSERT_PDF: { + if (editable) { + insert_text_at_caret(String::chr(0x202C)); + } + } break; + case MENU_INSERT_ALM: { + if (editable) { + insert_text_at_caret(String::chr(0x061C)); + } + } break; + case MENU_INSERT_LRI: { + if (editable) { + insert_text_at_caret(String::chr(0x2066)); + } + } break; + case MENU_INSERT_RLI: { + if (editable) { + insert_text_at_caret(String::chr(0x2067)); + } + } break; + case MENU_INSERT_FSI: { + if (editable) { + insert_text_at_caret(String::chr(0x2068)); + } + } break; + case MENU_INSERT_PDI: { + if (editable) { + insert_text_at_caret(String::chr(0x2069)); + } + } break; + case MENU_INSERT_ZWJ: { + if (editable) { + insert_text_at_caret(String::chr(0x200D)); + } + } break; + case MENU_INSERT_ZWNJ: { + if (editable) { + insert_text_at_caret(String::chr(0x200C)); + } + } break; + case MENU_INSERT_WJ: { + if (editable) { + insert_text_at_caret(String::chr(0x2060)); + } + } break; + case MENU_INSERT_SHY: { + if (editable) { + insert_text_at_caret(String::chr(0x00AD)); + } } } - return total_rows; } -void TextEdit::_update_wrap_at(bool p_force) { - int new_wrap_at = get_size().width - cache.style_normal->get_minimum_size().width - gutters_width - gutter_padding; - if (draw_minimap) { - new_wrap_at -= minimap_width; +/* Versioning */ +void TextEdit::begin_complex_operation() { + _push_current_op(); + if (complex_operation_count == 0) { + next_operation_is_complex = true; } - if (v_scroll->is_visible_in_tree()) { - new_wrap_at -= v_scroll->get_combined_minimum_size().width; + complex_operation_count++; +} + +void TextEdit::end_complex_operation() { + _push_current_op(); + ERR_FAIL_COND(undo_stack.size() == 0); + + complex_operation_count = MAX(complex_operation_count - 1, 0); + if (complex_operation_count > 0) { + return; + } + if (undo_stack.back()->get().chain_forward) { + undo_stack.back()->get().chain_forward = false; + return; } - new_wrap_at -= wrap_right_offset; // Give it a little more space. - if ((wrap_at != new_wrap_at) || p_force) { - wrap_at = new_wrap_at; - if (wrap_enabled) { - text.set_width(wrap_at); - } else { - text.set_width(-1); - } - text.invalidate_all_lines(); + undo_stack.back()->get().chain_backward = true; +} + +bool TextEdit::has_undo() const { + if (undo_stack_pos == nullptr) { + int pending = current_op.type == TextOperation::TYPE_NONE ? 0 : 1; + return undo_stack.size() + pending > 0; } + return undo_stack_pos != undo_stack.front(); +} - update_cursor_wrap_offset(); +bool TextEdit::has_redo() const { + return undo_stack_pos != nullptr; } -void TextEdit::adjust_viewport_to_cursor() { - // Make sure cursor is visible on the screen. - scrolling = false; - minimap_clicked = false; +void TextEdit::undo() { + if (!editable) { + return; + } - int cur_line = cursor.line; - int cur_wrap = get_cursor_wrap_index(); + _push_current_op(); - int first_vis_line = get_first_visible_line(); - int first_vis_wrap = cursor.wrap_ofs; - int last_vis_line = get_last_full_visible_line(); - int last_vis_wrap = get_last_full_visible_line_wrap_index(); + if (undo_stack_pos == nullptr) { + if (!undo_stack.size()) { + return; // Nothing to undo. + } - if (cur_line < first_vis_line || (cur_line == first_vis_line && cur_wrap < first_vis_wrap)) { - // Cursor is above screen. - set_line_as_first_visible(cur_line, cur_wrap); - } else if (cur_line > last_vis_line || (cur_line == last_vis_line && cur_wrap > last_vis_wrap)) { - // Cursor is below screen. - set_line_as_last_visible(cur_line, cur_wrap); - } + undo_stack_pos = undo_stack.back(); - int visible_width = get_size().width - cache.style_normal->get_minimum_size().width - gutters_width - gutter_padding - cache.minimap_width; - if (v_scroll->is_visible_in_tree()) { - visible_width -= v_scroll->get_combined_minimum_size().width; + } else if (undo_stack_pos == undo_stack.front()) { + return; // At the bottom of the undo stack. + } else { + undo_stack_pos = undo_stack_pos->prev(); } - visible_width -= 20; // Give it a little more space. - if (!is_wrap_enabled()) { - // Adjust x offset. - Vector2i cursor_pos; + deselect(); - // Get position of the start of caret. - if (ime_text.length() != 0 && ime_selection.x != 0) { - cursor_pos.x = get_column_x_offset_for_line(cursor.column + ime_selection.x, cursor.line); - } else { - cursor_pos.x = get_column_x_offset_for_line(cursor.column, cursor.line); - } + TextOperation op = undo_stack_pos->get(); + _do_text_op(op, true); + if (op.type != TextOperation::TYPE_INSERT && (op.from_line != op.to_line || op.to_column != op.from_column + 1)) { + select(op.from_line, op.from_column, op.to_line, op.to_column); + } - // Get position of the end of caret. - if (ime_text.length() != 0) { - if (ime_selection.y != 0) { - cursor_pos.y = get_column_x_offset_for_line(cursor.column + ime_selection.x + ime_selection.y, cursor.line); - } else { - cursor_pos.y = get_column_x_offset_for_line(cursor.column + ime_text.size(), cursor.line); + current_op.version = op.prev_version; + if (undo_stack_pos->get().chain_backward) { + while (true) { + ERR_BREAK(!undo_stack_pos->prev()); + undo_stack_pos = undo_stack_pos->prev(); + op = undo_stack_pos->get(); + _do_text_op(op, true); + current_op.version = op.prev_version; + if (undo_stack_pos->get().chain_forward) { + break; } - } else { - cursor_pos.y = cursor_pos.x; } + } - if (MAX(cursor_pos.x, cursor_pos.y) > (cursor.x_ofs + visible_width)) { - cursor.x_ofs = MAX(cursor_pos.x, cursor_pos.y) - visible_width + 1; - } + _update_scrollbars(); + if (undo_stack_pos->get().type == TextOperation::TYPE_REMOVE) { + set_caret_line(undo_stack_pos->get().to_line, false); + set_caret_column(undo_stack_pos->get().to_column); + } else { + set_caret_line(undo_stack_pos->get().from_line, false); + set_caret_column(undo_stack_pos->get().from_column); + } + update(); +} + +void TextEdit::redo() { + if (!editable) { + return; + } + _push_current_op(); + + if (undo_stack_pos == nullptr) { + return; // Nothing to do. + } + + deselect(); - if (MIN(cursor_pos.x, cursor_pos.y) < cursor.x_ofs) { - cursor.x_ofs = MIN(cursor_pos.x, cursor_pos.y); + TextOperation op = undo_stack_pos->get(); + _do_text_op(op, false); + current_op.version = op.version; + if (undo_stack_pos->get().chain_forward) { + while (true) { + ERR_BREAK(!undo_stack_pos->next()); + undo_stack_pos = undo_stack_pos->next(); + op = undo_stack_pos->get(); + _do_text_op(op, false); + current_op.version = op.version; + if (undo_stack_pos->get().chain_backward) { + break; + } } - } else { - cursor.x_ofs = 0; } - h_scroll->set_value(cursor.x_ofs); + _update_scrollbars(); + set_caret_line(undo_stack_pos->get().to_line, false); + set_caret_column(undo_stack_pos->get().to_column); + undo_stack_pos = undo_stack_pos->next(); update(); } -void TextEdit::center_viewport_to_cursor() { - // Move viewport so the cursor is in the center of the screen. - scrolling = false; - minimap_clicked = false; +void TextEdit::clear_undo_history() { + saved_version = 0; + current_op.type = TextOperation::TYPE_NONE; + undo_stack_pos = nullptr; + undo_stack.clear(); +} - set_line_as_center_visible(cursor.line, get_cursor_wrap_index()); - int visible_width = get_size().width - cache.style_normal->get_minimum_size().width - gutters_width - gutter_padding - cache.minimap_width; - if (v_scroll->is_visible_in_tree()) { - visible_width -= v_scroll->get_combined_minimum_size().width; +bool TextEdit::is_insert_text_operation() const { + return (current_op.type == TextOperation::TYPE_INSERT); +} + +void TextEdit::tag_saved_version() { + saved_version = get_version(); +} + +uint32_t TextEdit::get_version() const { + return current_op.version; +} + +uint32_t TextEdit::get_saved_version() const { + return saved_version; +} + +/* Search */ +void TextEdit::set_search_text(const String &p_search_text) { + search_text = p_search_text; +} + +void TextEdit::set_search_flags(uint32_t p_flags) { + search_flags = p_flags; +} + +Point2i TextEdit::search(const String &p_key, uint32_t p_search_flags, int p_from_line, int p_from_column) const { + if (p_key.length() == 0) { + return Point2(-1, -1); } - visible_width -= 20; // Give it a little more space. + ERR_FAIL_INDEX_V(p_from_line, text.size(), Point2i(-1, -1)); + ERR_FAIL_INDEX_V(p_from_column, text[p_from_line].length() + 1, Point2i(-1, -1)); - if (is_wrap_enabled()) { - // Center x offset. + // Search through the whole document, but start by current line. - Vector2i cursor_pos; + int line = p_from_line; + int pos = -1; - // Get position of the start of caret. - if (ime_text.length() != 0 && ime_selection.x != 0) { - cursor_pos.x = get_column_x_offset_for_line(cursor.column + ime_selection.x, cursor.line); - } else { - cursor_pos.x = get_column_x_offset_for_line(cursor.column, cursor.line); + for (int i = 0; i < text.size() + 1; i++) { + if (line < 0) { + line = text.size() - 1; + } + if (line == text.size()) { + line = 0; } - // Get position of the end of caret. - if (ime_text.length() != 0) { - if (ime_selection.y != 0) { - cursor_pos.y = get_column_x_offset_for_line(cursor.column + ime_selection.x + ime_selection.y, cursor.line); + String text_line = text[line]; + int from_column = 0; + if (line == p_from_line) { + if (i == text.size()) { + // Wrapped. + + if (p_search_flags & SEARCH_BACKWARDS) { + from_column = text_line.length(); + } else { + from_column = 0; + } + } else { - cursor_pos.y = get_column_x_offset_for_line(cursor.column + ime_text.size(), cursor.line); + from_column = p_from_column; } + } else { - cursor_pos.y = cursor_pos.x; + if (p_search_flags & SEARCH_BACKWARDS) { + from_column = text_line.length() - 1; + } else { + from_column = 0; + } } - if (MAX(cursor_pos.x, cursor_pos.y) > (cursor.x_ofs + visible_width)) { - cursor.x_ofs = MAX(cursor_pos.x, cursor_pos.y) - visible_width + 1; + pos = -1; + + int pos_from = (p_search_flags & SEARCH_BACKWARDS) ? text_line.length() : 0; + int last_pos = -1; + + while (true) { + if (p_search_flags & SEARCH_BACKWARDS) { + while ((last_pos = (p_search_flags & SEARCH_MATCH_CASE) ? text_line.rfind(p_key, pos_from) : text_line.rfindn(p_key, pos_from)) != -1) { + if (last_pos <= from_column) { + pos = last_pos; + break; + } + pos_from = last_pos - p_key.length(); + if (pos_from < 0) { + break; + } + } + } else { + while ((last_pos = (p_search_flags & SEARCH_MATCH_CASE) ? text_line.find(p_key, pos_from) : text_line.findn(p_key, pos_from)) != -1) { + if (last_pos >= from_column) { + pos = last_pos; + break; + } + pos_from = last_pos + p_key.length(); + } + } + + bool is_match = true; + + if (pos != -1 && (p_search_flags & SEARCH_WHOLE_WORDS)) { + // Validate for whole words. + if (pos > 0 && _is_text_char(text_line[pos - 1])) { + is_match = false; + } else if (pos + p_key.length() < text_line.length() && _is_text_char(text_line[pos + p_key.length()])) { + is_match = false; + } + } + + if (pos_from == -1) { + pos = -1; + } + + if (is_match || last_pos == -1 || pos == -1) { + break; + } + + pos_from = (p_search_flags & SEARCH_BACKWARDS) ? pos - 1 : pos + 1; + pos = -1; } - if (MIN(cursor_pos.x, cursor_pos.y) < cursor.x_ofs) { - cursor.x_ofs = MIN(cursor_pos.x, cursor_pos.y); + if (pos != -1) { + break; } - } else { - cursor.x_ofs = 0; - } - h_scroll->set_value(cursor.x_ofs); - update(); + if (p_search_flags & SEARCH_BACKWARDS) { + line--; + } else { + line++; + } + } + return (pos == -1) ? Point2i(-1, -1) : Point2i(pos, line); } -void TextEdit::update_cursor_wrap_offset() { - int first_vis_line = get_first_visible_line(); - if (line_wraps(first_vis_line)) { - cursor.wrap_ofs = MIN(cursor.wrap_ofs, times_line_wraps(first_vis_line)); - } else { - cursor.wrap_ofs = 0; +/* Mouse */ +Point2 TextEdit::get_local_mouse_pos() const { + Point2 mp = get_local_mouse_position(); + if (is_layout_rtl()) { + mp.x = get_size().width - mp.x; } - set_line_as_first_visible(cursor.line_ofs, cursor.wrap_ofs); + return mp; } -bool TextEdit::line_wraps(int line) const { - ERR_FAIL_INDEX_V(line, text.size(), 0); - if (!is_wrap_enabled()) { - return false; +String TextEdit::get_word_at_pos(const Vector2 &p_pos) const { + Point2i pos = get_line_column_at_pos(p_pos); + int row = pos.y; + int col = pos.x; + + String s = text[row]; + if (s.length() == 0) { + return ""; } - return text.get_line_wrap_amount(line) > 0; + int beg, end; + if (select_word(s, col, beg, end)) { + bool inside_quotes = false; + char32_t selected_quote = '\0'; + int qbegin = 0, qend = 0; + for (int i = 0; i < s.length(); i++) { + if (s[i] == '"' || s[i] == '\'') { + if (i == 0 || s[i - 1] != '\\') { + if (inside_quotes && selected_quote == s[i]) { + qend = i; + inside_quotes = false; + selected_quote = '\0'; + if (col >= qbegin && col <= qend) { + return s.substr(qbegin, qend - qbegin); + } + } else if (!inside_quotes) { + qbegin = i + 1; + inside_quotes = true; + selected_quote = s[i]; + } + } + } + } + + return s.substr(beg, end - beg); + } + + return String(); } -int TextEdit::times_line_wraps(int line) const { - ERR_FAIL_INDEX_V(line, text.size(), 0); +Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos) const { + float rows = p_pos.y; + rows -= style_normal->get_margin(SIDE_TOP); + rows /= get_line_height(); + rows += _get_v_scroll_offset(); + int first_vis_line = get_first_visible_line(); + int row = first_vis_line + Math::floor(rows); + int wrap_index = 0; - if (!line_wraps(line)) { - return 0; + if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE || _is_hiding_enabled()) { + Point2i f_ofs = get_next_visible_line_index_offset_from(first_vis_line, caret.wrap_ofs, rows + (1 * SGN(rows))); + wrap_index = f_ofs.y; + if (rows < 0) { + row = first_vis_line - (f_ofs.x - 1); + } else { + row = first_vis_line + (f_ofs.x - 1); + } } - return text.get_line_wrap_amount(line); + if (row < 0) { + row = 0; + } + + int col = 0; + + if (row >= text.size()) { + row = text.size() - 1; + col = text[row].size(); + } else { + int colx = p_pos.x - (style_normal->get_margin(SIDE_LEFT) + gutters_width + gutter_padding); + colx += caret.x_ofs; + col = _get_char_pos_for_line(colx, row, wrap_index); + if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && wrap_index < get_line_wrap_count(row)) { + // Move back one if we are at the end of the row. + Vector<String> rows2 = get_line_wrapped_text(row); + int row_end_col = 0; + for (int i = 0; i < wrap_index + 1; i++) { + row_end_col += rows2[i].length(); + } + if (col >= row_end_col) { + col -= 1; + } + } + + RID text_rid = text.get_line_data(row)->get_line_rid(wrap_index); + if (is_layout_rtl()) { + colx = TS->shaped_text_get_size(text_rid).x - colx; + } + col = TS->shaped_text_hit_test_position(text_rid, colx); + } + + return Point2i(col, row); } -Vector<String> TextEdit::get_wrap_rows_text(int p_line) const { - ERR_FAIL_INDEX_V(p_line, text.size(), Vector<String>()); +int TextEdit::get_minimap_line_at_pos(const Point2i &p_pos) const { + float rows = p_pos.y; + rows -= style_normal->get_margin(SIDE_TOP); + rows /= (minimap_char_size.y + minimap_line_spacing); + rows += _get_v_scroll_offset(); - Vector<String> lines; - if (!line_wraps(p_line)) { - lines.push_back(text[p_line]); - return lines; + // calculate visible lines + int minimap_visible_lines = get_minimap_visible_lines(); + int visible_rows = get_visible_line_count() + 1; + int first_visible_line = get_first_visible_line() - 1; + int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0); + draw_amount += get_line_wrap_count(first_visible_line + 1); + int minimap_line_height = (minimap_char_size.y + minimap_line_spacing); + + // calculate viewport size and y offset + int viewport_height = (draw_amount - 1) * minimap_line_height; + int control_height = _get_control_height() - viewport_height; + int viewport_offset_y = round(get_scroll_pos_for_line(first_visible_line + 1) * control_height) / ((v_scroll->get_max() <= minimap_visible_lines) ? (minimap_visible_lines - draw_amount) : (v_scroll->get_max() - draw_amount)); + + // calculate the first line. + int num_lines_before = round((viewport_offset_y) / minimap_line_height); + int minimap_line = (v_scroll->get_max() <= minimap_visible_lines) ? -1 : first_visible_line; + if (first_visible_line > 0 && minimap_line >= 0) { + minimap_line -= get_next_visible_line_index_offset_from(first_visible_line, 0, -num_lines_before).x; + minimap_line -= (minimap_line > 0 && smooth_scroll_enabled ? 1 : 0); + } else { + minimap_line = 0; } - const String &line_text = text[p_line]; - Vector<Vector2i> line_ranges = text.get_line_wrap_ranges(p_line); - for (int i = 0; i < line_ranges.size(); i++) { - lines.push_back(line_text.substr(line_ranges[i].x, line_ranges[i].y - line_ranges[i].x)); + int row = minimap_line + Math::floor(rows); + if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE || _is_hiding_enabled()) { + int f_ofs = get_next_visible_line_index_offset_from(minimap_line, caret.wrap_ofs, rows + (1 * SGN(rows))).x - 1; + if (rows < 0) { + row = minimap_line - f_ofs; + } else { + row = minimap_line + f_ofs; + } } - return lines; + if (row < 0) { + row = 0; + } + + if (row >= text.size()) { + row = text.size() - 1; + } + + return row; } -int TextEdit::get_cursor_wrap_index() const { - return get_line_wrap_index_at_col(cursor.line, cursor.column); +bool TextEdit::is_dragging_cursor() const { + return dragging_selection || dragging_minimap; } -int TextEdit::get_line_wrap_index_at_col(int p_line, int p_column) const { - ERR_FAIL_INDEX_V(p_line, text.size(), 0); +/* Caret */ +void TextEdit::set_caret_type(CaretType p_type) { + caret_type = p_type; + update(); +} - if (!line_wraps(p_line)) { - return 0; - } +TextEdit::CaretType TextEdit::get_caret_type() const { + return caret_type; +} - // Loop through wraps in the line text until we get to the column. - int wrap_index = 0; - int col = 0; - Vector<String> rows = get_wrap_rows_text(p_line); - for (int i = 0; i < rows.size(); i++) { - wrap_index = i; - String s = rows[wrap_index]; - col += s.length(); - if (col > p_column) { - break; +void TextEdit::set_caret_blink_enabled(const bool p_enabled) { + caret_blink_enabled = p_enabled; + + if (has_focus()) { + if (p_enabled) { + caret_blink_timer->start(); + } else { + caret_blink_timer->stop(); } } - return wrap_index; + draw_caret = true; } -void TextEdit::set_mid_grapheme_caret_enabled(const bool p_enabled) { - mid_grapheme_caret_enabled = p_enabled; +bool TextEdit::is_caret_blink_enabled() const { + return caret_blink_enabled; } -bool TextEdit::get_mid_grapheme_caret_enabled() const { - return mid_grapheme_caret_enabled; +float TextEdit::get_caret_blink_speed() const { + return caret_blink_timer->get_wait_time(); } -void TextEdit::cursor_set_column(int p_col, bool p_adjust_viewport) { - if (p_col < 0) { - p_col = 0; - } +void TextEdit::set_caret_blink_speed(const float p_speed) { + ERR_FAIL_COND(p_speed <= 0); + caret_blink_timer->set_wait_time(p_speed); +} - cursor.column = p_col; - if (cursor.column > get_line(cursor.line).length()) { - cursor.column = get_line(cursor.line).length(); - } +void TextEdit::set_move_caret_on_right_click_enabled(const bool p_enable) { + move_caret_on_right_click = p_enable; +} - cursor.last_fit_x = get_column_x_offset_for_line(cursor.column, cursor.line); +bool TextEdit::is_move_caret_on_right_click_enabled() const { + return move_caret_on_right_click; +} - if (p_adjust_viewport) { - adjust_viewport_to_cursor(); - } +void TextEdit::set_caret_mid_grapheme_enabled(const bool p_enabled) { + caret_mid_grapheme_enabled = p_enabled; +} - if (!cursor_changed_dirty) { - if (is_inside_tree()) { - MessageQueue::get_singleton()->push_call(this, "_cursor_changed_emit"); - } - cursor_changed_dirty = true; - } +bool TextEdit::is_caret_mid_grapheme_enabled() const { + return caret_mid_grapheme_enabled; } -void TextEdit::cursor_set_line(int p_row, bool p_adjust_viewport, bool p_can_be_hidden, int p_wrap_index) { - if (setting_row) { +bool TextEdit::is_caret_visible() const { + return caret.visible; +} + +Point2 TextEdit::get_caret_draw_pos() const { + return caret.draw_pos; +} + +void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_hidden, int p_wrap_index) { + if (setting_caret_line) { return; } - setting_row = true; - if (p_row < 0) { - p_row = 0; + setting_caret_line = true; + if (p_line < 0) { + p_line = 0; } - if (p_row >= text.size()) { - p_row = text.size() - 1; + if (p_line >= text.size()) { + p_line = text.size() - 1; } if (!p_can_be_hidden) { - if (is_line_hidden(CLAMP(p_row, 0, text.size() - 1))) { - int move_down = num_lines_from(p_row, 1) - 1; - if (p_row + move_down <= text.size() - 1 && !is_line_hidden(p_row + move_down)) { - p_row += move_down; + if (_is_line_hidden(CLAMP(p_line, 0, text.size() - 1))) { + int move_down = get_next_visible_line_offset_from(p_line, 1) - 1; + if (p_line + move_down <= text.size() - 1 && !_is_line_hidden(p_line + move_down)) { + p_line += move_down; } else { - int move_up = num_lines_from(p_row, -1) - 1; - if (p_row - move_up > 0 && !is_line_hidden(p_row - move_up)) { - p_row -= move_up; + int move_up = get_next_visible_line_offset_from(p_line, -1) - 1; + if (p_line - move_up > 0 && !_is_line_hidden(p_line - move_up)) { + p_line -= move_up; } else { - WARN_PRINT(("Cursor set to hidden line " + itos(p_row) + " and there are no nonhidden lines.")); + WARN_PRINT(("Caret set to hidden line " + itos(p_line) + " and there are no nonhidden lines.")); } } } } - cursor.line = p_row; + caret.line = p_line; - int n_col = get_char_pos_for_line(cursor.last_fit_x, p_row, p_wrap_index); - if (n_col != 0 && is_wrap_enabled() && p_wrap_index < times_line_wraps(p_row)) { - Vector<String> rows = get_wrap_rows_text(p_row); + int n_col = _get_char_pos_for_line(caret.last_fit_x, p_line, p_wrap_index); + if (n_col != 0 && get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && p_wrap_index < get_line_wrap_count(p_line)) { + Vector<String> rows = get_line_wrapped_text(p_line); int row_end_col = 0; for (int i = 0; i < p_wrap_index + 1; i++) { row_end_col += rows[i].length(); @@ -3549,84 +3547,87 @@ void TextEdit::cursor_set_line(int p_row, bool p_adjust_viewport, bool p_can_be_ n_col -= 1; } } - cursor.column = n_col; + caret.column = n_col; if (p_adjust_viewport) { - adjust_viewport_to_cursor(); + adjust_viewport_to_caret(); } - setting_row = false; + setting_caret_line = false; - if (!cursor_changed_dirty) { + if (!caret_pos_dirty) { if (is_inside_tree()) { - MessageQueue::get_singleton()->push_call(this, "_cursor_changed_emit"); + MessageQueue::get_singleton()->push_call(this, "_emit_caret_changed"); } - cursor_changed_dirty = true; + caret_pos_dirty = true; } } -Point2 TextEdit::get_caret_draw_pos() const { - return cursor.draw_pos; -} - -bool TextEdit::is_caret_visible() const { - return cursor.visible; +int TextEdit::get_caret_line() const { + return caret.line; } -int TextEdit::cursor_get_column() const { - return cursor.column; -} +void TextEdit::set_caret_column(int p_col, bool p_adjust_viewport) { + if (p_col < 0) { + p_col = 0; + } -int TextEdit::cursor_get_line() const { - return cursor.line; -} + caret.column = p_col; + if (caret.column > get_line(caret.line).length()) { + caret.column = get_line(caret.line).length(); + } -bool TextEdit::cursor_get_blink_enabled() const { - return caret_blink_enabled; -} + caret.last_fit_x = _get_column_x_offset_for_line(caret.column, caret.line); -void TextEdit::cursor_set_blink_enabled(const bool p_enabled) { - caret_blink_enabled = p_enabled; + if (p_adjust_viewport) { + adjust_viewport_to_caret(); + } - if (has_focus()) { - if (p_enabled) { - caret_blink_timer->start(); - } else { - caret_blink_timer->stop(); + if (!caret_pos_dirty) { + if (is_inside_tree()) { + MessageQueue::get_singleton()->push_call(this, "_emit_caret_changed"); } + caret_pos_dirty = true; } - - draw_caret = true; } -float TextEdit::cursor_get_blink_speed() const { - return caret_blink_timer->get_wait_time(); +int TextEdit::get_caret_column() const { + return caret.column; } -void TextEdit::cursor_set_blink_speed(const float p_speed) { - ERR_FAIL_COND(p_speed <= 0); - caret_blink_timer->set_wait_time(p_speed); +int TextEdit::get_caret_wrap_index() const { + return get_line_wrap_index_at_column(caret.line, caret.column); } -void TextEdit::cursor_set_block_mode(const bool p_enable) { - block_caret = p_enable; - update(); +String TextEdit::get_word_under_caret() const { + Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid()); + for (int i = 0; i < words.size(); i++) { + if (words[i].x <= caret.column && words[i].y > caret.column) { + return text[caret.line].substr(words[i].x, words[i].y - words[i].x); + } + } + return ""; } -bool TextEdit::cursor_is_block_mode() const { - return block_caret; +/* Selection. */ +void TextEdit::set_selecting_enabled(const bool p_enabled) { + selecting_enabled = p_enabled; + + if (!selecting_enabled) { + deselect(); + } } -void TextEdit::set_right_click_moves_caret(bool p_enable) { - right_click_moves_caret = p_enable; +bool TextEdit::is_selecting_enabled() const { + return selecting_enabled; } -bool TextEdit::is_right_click_moving_caret() const { - return right_click_moves_caret; +void TextEdit::set_override_selected_font_color(bool p_override_selected_font_color) { + override_selected_font_color = p_override_selected_font_color; } -TextEdit::SelectionMode TextEdit::get_selection_mode() const { - return selection.selecting_mode; +bool TextEdit::is_overriding_selected_font_color() const { + return override_selected_font_color; } void TextEdit::set_selection_mode(SelectionMode p_mode, int p_line, int p_column) { @@ -3641,475 +3642,544 @@ void TextEdit::set_selection_mode(SelectionMode p_mode, int p_line, int p_column } } -int TextEdit::get_selection_line() const { - return selection.selecting_line; -}; - -int TextEdit::get_selection_column() const { - return selection.selecting_column; -}; - -void TextEdit::_v_scroll_input() { - scrolling = false; - minimap_clicked = false; +TextEdit::SelectionMode TextEdit::get_selection_mode() const { + return selection.selecting_mode; } -void TextEdit::_scroll_moved(double p_to_val) { - if (updating_scrolls) { +void TextEdit::select_all() { + if (!selecting_enabled) { return; } - if (h_scroll->is_visible_in_tree()) { - cursor.x_ofs = h_scroll->get_value(); - } - if (v_scroll->is_visible_in_tree()) { - // Set line ofs and wrap ofs. - int v_scroll_i = floor(get_v_scroll()); - int sc = 0; - int n_line; - for (n_line = 0; n_line < text.size(); n_line++) { - if (!is_line_hidden(n_line)) { - sc++; - sc += times_line_wraps(n_line); - if (sc > v_scroll_i) { - break; - } - } - } - n_line = MIN(n_line, text.size() - 1); - int line_wrap_amount = times_line_wraps(n_line); - int wi = line_wrap_amount - (sc - v_scroll_i - 1); - wi = CLAMP(wi, 0, line_wrap_amount); - - cursor.line_ofs = n_line; - cursor.wrap_ofs = wi; + if (text.size() == 1 && text[0].length() == 0) { + return; } + selection.active = true; + selection.from_line = 0; + selection.from_column = 0; + selection.selecting_line = 0; + selection.selecting_column = 0; + selection.to_line = text.size() - 1; + selection.to_column = text[selection.to_line].length(); + selection.selecting_mode = SelectionMode::SELECTION_MODE_SHIFT; + selection.shiftclick_left = true; + set_caret_line(selection.to_line, false); + set_caret_column(selection.to_column, false); update(); } -int TextEdit::get_row_height() const { - int height = cache.font->get_height(cache.font_size); - for (int i = 0; i < text.size(); i++) { - for (int j = 0; j <= text.get_line_wrap_amount(i); j++) { - height = MAX(height, text.get_line_height(i, j)); - } +void TextEdit::select_word_under_caret() { + if (!selecting_enabled) { + return; } - return height + cache.line_spacing; -} -int TextEdit::get_char_pos_for_line(int p_px, int p_line, int p_wrap_index) const { - ERR_FAIL_INDEX_V(p_line, text.size(), 0); - p_wrap_index = MIN(p_wrap_index, text.get_line_data(p_line)->get_line_count() - 1); - - RID text_rid = text.get_line_data(p_line)->get_line_rid(p_wrap_index); - if (is_layout_rtl()) { - p_px = TS->shaped_text_get_size(text_rid).x - p_px; + if (text.size() == 1 && text[0].length() == 0) { + return; } - return TS->shaped_text_hit_test_position(text_rid, p_px); -} -int TextEdit::get_column_x_offset_for_line(int p_char, int p_line) const { - ERR_FAIL_INDEX_V(p_line, text.size(), 0); + if (selection.active) { + /* Allow toggling selection by pressing the shortcut a second time. */ + /* This is also usable as a general-purpose "deselect" shortcut after */ + /* selecting anything. */ + deselect(); + return; + } - 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)) { - row = i; + int begin = 0; + int end = 0; + const Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid()); + for (int i = 0; i < words.size(); i++) { + if (words[i].x <= caret.column && words[i].y >= caret.column) { + begin = words[i].x; + end = words[i].y; break; } } - Rect2 l_caret, t_caret; - TextServer::Direction l_dir, t_dir; - RID text_rid = text.get_line_data(p_line)->get_line_rid(row); - TS->shaped_text_get_carets(text_rid, cursor.column, l_caret, l_dir, t_caret, t_dir); - if ((l_caret != Rect2() && (l_dir == TextServer::DIRECTION_AUTO || l_dir == (TextServer::Direction)input_direction)) || (t_caret == Rect2())) { - return l_caret.position.x; - } else { - return t_caret.position.x; - } + select(caret.line, begin, caret.line, end); + /* Move the caret to the end of the word for easier editing. */ + set_caret_column(end, false); } -void TextEdit::insert_text_at_cursor(const String &p_text) { - if (selection.active) { - cursor_set_line(selection.from_line, false); - cursor_set_column(selection.from_column); - - _remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column); - selection.active = false; - selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE; +void TextEdit::select(int p_from_line, int p_from_column, int p_to_line, int p_to_column) { + if (!selecting_enabled) { + return; } - _insert_text_at_cursor(p_text); - update(); -} + if (p_from_line < 0) { + p_from_line = 0; + } else if (p_from_line >= text.size()) { + p_from_line = text.size() - 1; + } + if (p_from_column >= text[p_from_line].length()) { + p_from_column = text[p_from_line].length(); + } + if (p_from_column < 0) { + p_from_column = 0; + } -Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const { - if (highlighted_word != String()) { - return CURSOR_POINTING_HAND; + if (p_to_line < 0) { + p_to_line = 0; + } else if (p_to_line >= text.size()) { + p_to_line = text.size() - 1; + } + if (p_to_column >= text[p_to_line].length()) { + p_to_column = text[p_to_line].length(); + } + if (p_to_column < 0) { + p_to_column = 0; } - int row, col; - _get_mouse_pos(p_pos, row, col); + selection.from_line = p_from_line; + selection.from_column = p_from_column; + selection.to_line = p_to_line; + selection.to_column = p_to_column; - int left_margin = cache.style_normal->get_margin(SIDE_LEFT); - int gutter = left_margin + gutters_width; - if (p_pos.x < gutter) { - for (int i = 0; i < gutters.size(); i++) { - if (!gutters[i].draw) { - continue; - } + selection.active = true; - if (p_pos.x > left_margin && p_pos.x <= (left_margin + gutters[i].width) - 3) { - if (gutters[i].clickable || is_line_gutter_clickable(row, i)) { - return CURSOR_POINTING_HAND; - } - } - left_margin += gutters[i].width; + if (selection.from_line == selection.to_line) { + if (selection.from_column == selection.to_column) { + selection.active = false; + + } else if (selection.from_column > selection.to_column) { + selection.shiftclick_left = false; + SWAP(selection.from_column, selection.to_column); + } else { + selection.shiftclick_left = true; } - return CURSOR_ARROW; + } else if (selection.from_line > selection.to_line) { + selection.shiftclick_left = false; + SWAP(selection.from_line, selection.to_line); + SWAP(selection.from_column, selection.to_column); + } else { + selection.shiftclick_left = true; } - int xmargin_end = get_size().width - cache.style_normal->get_margin(SIDE_RIGHT); - if (draw_minimap && p_pos.x > xmargin_end - minimap_width && p_pos.x <= xmargin_end) { - return CURSOR_ARROW; - } - return get_default_cursor_shape(); + update(); } -void TextEdit::set_text(String p_text) { - setting_text = true; - if (!undo_enabled) { - _clear(); - _insert_text_at_cursor(p_text); +bool TextEdit::has_selection() const { + return selection.active; +} + +String TextEdit::get_selected_text() const { + if (!selection.active) { + return ""; } - if (undo_enabled) { - cursor_set_line(0); - cursor_set_column(0); + return _base_get_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column); +} - begin_complex_operation(); - _remove_text(0, 0, MAX(0, get_line_count() - 1), MAX(get_line(MAX(get_line_count() - 1, 0)).size() - 1, 0)); - _insert_text_at_cursor(p_text); - end_complex_operation(); - selection.active = false; - } +int TextEdit::get_selection_line() const { + return selection.selecting_line; +} + +int TextEdit::get_selection_column() const { + return selection.selecting_column; +} - cursor_set_line(0); - cursor_set_column(0); +int TextEdit::get_selection_from_line() const { + ERR_FAIL_COND_V(!selection.active, -1); + return selection.from_line; +} + +int TextEdit::get_selection_from_column() const { + ERR_FAIL_COND_V(!selection.active, -1); + return selection.from_column; +} + +int TextEdit::get_selection_to_line() const { + ERR_FAIL_COND_V(!selection.active, -1); + return selection.to_line; +} + +int TextEdit::get_selection_to_column() const { + ERR_FAIL_COND_V(!selection.active, -1); + return selection.to_column; +} +void TextEdit::deselect() { + selection.active = false; update(); - setting_text = false; } -String TextEdit::get_text() { - String longthing; - int len = text.size(); - for (int i = 0; i < len; i++) { - longthing += text[i]; - if (i != len - 1) { - longthing += "\n"; - } +void TextEdit::delete_selection() { + if (!has_selection()) { + return; } - return longthing; + selection.active = false; + selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE; + _remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column); + set_caret_line(selection.from_line, false, false); + set_caret_column(selection.from_column); + update(); } -void TextEdit::set_structured_text_bidi_override(Control::StructuredTextParser p_parser) { - if (st_parser != p_parser) { - st_parser = p_parser; - for (int i = 0; i < text.size(); i++) { - text.set(i, text[i], structured_text_parser(st_parser, st_args, text[i])); - } - update(); +/* line wrapping. */ +void TextEdit::set_line_wrapping_mode(LineWrappingMode p_wrapping_mode) { + if (line_wrapping_mode != p_wrapping_mode) { + line_wrapping_mode = p_wrapping_mode; + _update_wrap_at_column(true); } } -Control::StructuredTextParser TextEdit::get_structured_text_bidi_override() const { - return st_parser; +TextEdit::LineWrappingMode TextEdit::get_line_wrapping_mode() const { + return line_wrapping_mode; } -void TextEdit::set_structured_text_bidi_override_options(Array p_args) { - st_args = p_args; - for (int i = 0; i < text.size(); i++) { - text.set(i, text[i], structured_text_parser(st_parser, st_args, text[i])); +bool TextEdit::is_line_wrapped(int p_line) const { + ERR_FAIL_INDEX_V(p_line, text.size(), 0); + if (get_line_wrapping_mode() == LineWrappingMode::LINE_WRAPPING_NONE) { + return false; } - update(); + return text.get_line_wrap_amount(p_line) > 0; } -Array TextEdit::get_structured_text_bidi_override_options() const { - return st_args; +int TextEdit::get_line_wrap_count(int p_line) const { + ERR_FAIL_INDEX_V(p_line, text.size(), 0); + + if (!is_line_wrapped(p_line)) { + return 0; + } + + return text.get_line_wrap_amount(p_line); } -void TextEdit::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) { - text_direction = p_text_direction; - if (text_direction != TEXT_DIRECTION_AUTO && text_direction != TEXT_DIRECTION_INHERITED) { - input_direction = text_direction; - } - TextServer::Direction dir; - if (text_direction == Control::TEXT_DIRECTION_INHERITED) { - dir = is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR; - } else { - dir = (TextServer::Direction)text_direction; +int TextEdit::get_line_wrap_index_at_column(int p_line, int p_column) const { + ERR_FAIL_INDEX_V(p_line, text.size(), 0); + ERR_FAIL_COND_V(p_column < 0, 0); + ERR_FAIL_COND_V(p_column > text[p_line].length(), 0); + + if (!is_line_wrapped(p_line)) { + return 0; + } + + /* Loop through wraps in the line text until we get to the column. */ + int wrap_index = 0; + int col = 0; + Vector<String> lines = get_line_wrapped_text(p_line); + for (int i = 0; i < lines.size(); i++) { + wrap_index = i; + String s = lines[wrap_index]; + col += s.length(); + if (col > p_column) { + break; } - text.set_direction_and_language(dir, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); - text.invalidate_all(); + } + return wrap_index; +} - menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_INHERITED), text_direction == TEXT_DIRECTION_INHERITED); - menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_AUTO), text_direction == TEXT_DIRECTION_AUTO); - menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_LTR), text_direction == TEXT_DIRECTION_LTR); - menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_RTL), text_direction == TEXT_DIRECTION_RTL); - update(); +Vector<String> TextEdit::get_line_wrapped_text(int p_line) const { + ERR_FAIL_INDEX_V(p_line, text.size(), Vector<String>()); + + Vector<String> lines; + if (!is_line_wrapped(p_line)) { + lines.push_back(text[p_line]); + return lines; + } + + const String &line_text = text[p_line]; + Vector<Vector2i> line_ranges = text.get_line_wrap_ranges(p_line); + for (int i = 0; i < line_ranges.size(); i++) { + lines.push_back(line_text.substr(line_ranges[i].x, line_ranges[i].y - line_ranges[i].x)); } + + return lines; } -Control::TextDirection TextEdit::get_text_direction() const { - return text_direction; +/* Viewport */ +// Scrolling. +void TextEdit::set_smooth_scroll_enabled(const bool p_enable) { + v_scroll->set_smooth_scroll_enabled(p_enable); + smooth_scroll_enabled = p_enable; } -void TextEdit::clear_opentype_features() { - opentype_features.clear(); - text.set_font_features(opentype_features); - text.invalidate_all(); +bool TextEdit::is_smooth_scroll_enabled() const { + return smooth_scroll_enabled; +} + +void TextEdit::set_scroll_past_end_of_file_enabled(const bool p_enabled) { + scroll_past_end_of_file_enabled = p_enabled; update(); } -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_all(); - update(); - } +bool TextEdit::is_scroll_past_end_of_file_enabled() const { + return scroll_past_end_of_file_enabled; } -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; +void TextEdit::set_v_scroll(double p_scroll) { + v_scroll->set_value(p_scroll); + int max_v_scroll = v_scroll->get_max() - v_scroll->get_page(); + if (p_scroll >= max_v_scroll - 1.0) { + _scroll_moved(v_scroll->get_value()); } - return opentype_features[tag]; } -void TextEdit::set_language(const String &p_language) { - if (language != p_language) { - language = p_language; - TextServer::Direction dir; - if (text_direction == Control::TEXT_DIRECTION_INHERITED) { - dir = is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR; - } else { - dir = (TextServer::Direction)text_direction; - } - text.set_direction_and_language(dir, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); - text.invalidate_all(); - update(); +double TextEdit::get_v_scroll() const { + return v_scroll->get_value(); +} + +void TextEdit::set_h_scroll(int p_scroll) { + if (p_scroll < 0) { + p_scroll = 0; } + h_scroll->set_value(p_scroll); } -String TextEdit::get_language() const { - return language; +int TextEdit::get_h_scroll() const { + return h_scroll->get_value(); } -void TextEdit::set_draw_control_chars(bool p_draw_control_chars) { - if (draw_control_chars != p_draw_control_chars) { - draw_control_chars = p_draw_control_chars; - menu->set_item_checked(menu->get_item_index(MENU_DISPLAY_UCC), draw_control_chars); - text.set_draw_control_chars(draw_control_chars); - text.invalidate_all(); - update(); - } +void TextEdit::set_v_scroll_speed(float p_speed) { + v_scroll_speed = p_speed; } -bool TextEdit::get_draw_control_chars() const { - return draw_control_chars; +float TextEdit::get_v_scroll_speed() const { + return v_scroll_speed; } -String TextEdit::get_text_for_lookup_completion() { - int row, col; - Point2i mp = _get_local_mouse_pos(); - _get_mouse_pos(mp, row, col); +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); + ERR_FAIL_COND_V(p_wrap_index > get_line_wrap_count(p_line), 0); - String longthing; - int len = text.size(); - for (int i = 0; i < len; i++) { - if (i == row) { - longthing += text[i].substr(0, col); - longthing += String::chr(0xFFFF); // Not unicode, represents the cursor. - longthing += text[i].substr(col, text[i].size()); - } else { - longthing += text[i]; - } + if (get_line_wrapping_mode() == LineWrappingMode::LINE_WRAPPING_NONE && !_is_hiding_enabled()) { + return p_line; + } - if (i != len - 1) { - longthing += "\n"; + // Count the number of visible lines up to this line. + double new_line_scroll_pos = 0.0; + int to = CLAMP(p_line, 0, text.size() - 1); + for (int i = 0; i < to; i++) { + if (!text.is_hidden(i)) { + new_line_scroll_pos++; + new_line_scroll_pos += get_line_wrap_count(i); } } + new_line_scroll_pos += p_wrap_index; + return new_line_scroll_pos; +} - return longthing; +// Visible lines. +void TextEdit::set_line_as_first_visible(int p_line, int p_wrap_index) { + ERR_FAIL_INDEX(p_line, text.size()); + ERR_FAIL_COND(p_wrap_index < 0); + ERR_FAIL_COND(p_wrap_index > get_line_wrap_count(p_line)); + set_v_scroll(get_scroll_pos_for_line(p_line, p_wrap_index)); } -String TextEdit::get_line(int line) const { - if (line < 0 || line >= text.size()) { - return ""; - } +int TextEdit::get_first_visible_line() const { + return CLAMP(caret.line_ofs, 0, text.size() - 1); +} - return text[line]; -}; +void TextEdit::set_line_as_center_visible(int p_line, int p_wrap_index) { + ERR_FAIL_INDEX(p_line, text.size()); + ERR_FAIL_COND(p_wrap_index < 0); + ERR_FAIL_COND(p_wrap_index > get_line_wrap_count(p_line)); -bool TextEdit::has_ime_text() const { - return !ime_text.is_empty(); -} + 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); + int first_line = p_line - next_line.x + 1; -void TextEdit::_clear() { - clear_undo_history(); - text.clear(); - cursor.column = 0; - cursor.line = 0; - cursor.x_ofs = 0; - cursor.line_ofs = 0; - cursor.wrap_ofs = 0; - cursor.last_fit_x = 0; - selection.active = false; + set_v_scroll(get_scroll_pos_for_line(first_line, next_line.y)); } -void TextEdit::clear() { - setting_text = true; - _clear(); - setting_text = false; -}; - -void TextEdit::set_readonly(bool p_readonly) { - if (readonly == p_readonly) { - return; - } +void TextEdit::set_line_as_last_visible(int p_line, int p_wrap_index) { + ERR_FAIL_INDEX(p_line, text.size()); + ERR_FAIL_COND(p_wrap_index < 0); + ERR_FAIL_COND(p_wrap_index > get_line_wrap_count(p_line)); - readonly = p_readonly; - _generate_context_menu(); + 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; - update(); + set_v_scroll(get_scroll_pos_for_line(first_line, next_line.y) + _get_visible_lines_offset()); } -bool TextEdit::is_readonly() const { - return readonly; +int TextEdit::get_last_full_visible_line() const { + int first_vis_line = get_first_visible_line(); + int last_vis_line = 0; + last_vis_line = first_vis_line + get_next_visible_line_index_offset_from(first_vis_line, caret.wrap_ofs, get_visible_line_count()).x - 1; + last_vis_line = CLAMP(last_vis_line, 0, text.size() - 1); + return last_vis_line; } -void TextEdit::set_wrap_enabled(bool p_wrap_enabled) { - if (wrap_enabled != p_wrap_enabled) { - wrap_enabled = p_wrap_enabled; - _update_wrap_at(true); - } +int TextEdit::get_last_full_visible_line_wrap_index() const { + int first_vis_line = get_first_visible_line(); + return get_next_visible_line_index_offset_from(first_vis_line, caret.wrap_ofs, get_visible_line_count()).y; } -bool TextEdit::is_wrap_enabled() const { - return wrap_enabled; +int TextEdit::get_visible_line_count() const { + return _get_control_height() / get_line_height(); } -void TextEdit::set_max_chars(int p_max_chars) { - max_chars = p_max_chars; -} +int TextEdit::get_total_visible_line_count() const { + /* Returns the total number of (lines + wraped - hidden). */ + if (!_is_hiding_enabled() && get_line_wrapping_mode() == LineWrappingMode::LINE_WRAPPING_NONE) { + return text.size(); + } -int TextEdit::get_max_chars() const { - return max_chars; + int total_rows = 0; + for (int i = 0; i < text.size(); i++) { + if (!text.is_hidden(i)) { + total_rows++; + total_rows += get_line_wrap_count(i); + } + } + return total_rows; } -void TextEdit::_reset_caret_blink_timer() { - if (caret_blink_enabled) { - draw_caret = true; - if (has_focus()) { - caret_blink_timer->stop(); - caret_blink_timer->start(); - update(); +// Auto adjust +void TextEdit::adjust_viewport_to_caret() { + // Make sure Caret is visible on the screen. + scrolling = false; + minimap_clicked = false; + + int cur_line = caret.line; + int cur_wrap = get_caret_wrap_index(); + + int first_vis_line = get_first_visible_line(); + int first_vis_wrap = caret.wrap_ofs; + int last_vis_line = get_last_full_visible_line(); + int last_vis_wrap = get_last_full_visible_line_wrap_index(); + + if (cur_line < first_vis_line || (cur_line == first_vis_line && cur_wrap < first_vis_wrap)) { + // Caret is above screen. + set_line_as_first_visible(cur_line, cur_wrap); + } else if (cur_line > last_vis_line || (cur_line == last_vis_line && cur_wrap > last_vis_wrap)) { + // Caret is below screen. + set_line_as_last_visible(cur_line, cur_wrap); + } + + int visible_width = get_size().width - style_normal->get_minimum_size().width - gutters_width - gutter_padding; + if (draw_minimap) { + visible_width -= minimap_width; + } + if (v_scroll->is_visible_in_tree()) { + visible_width -= v_scroll->get_combined_minimum_size().width; + } + visible_width -= 20; // Give it a little more space. + + if (get_line_wrapping_mode() == LineWrappingMode::LINE_WRAPPING_NONE) { + // Adjust x offset. + Vector2i caret_pos; + + // Get position of the start of caret. + if (ime_text.length() != 0 && ime_selection.x != 0) { + caret_pos.x = _get_column_x_offset_for_line(caret.column + ime_selection.x, caret.line); + } else { + caret_pos.x = _get_column_x_offset_for_line(caret.column, caret.line); } + + // Get position of the end of caret. + if (ime_text.length() != 0) { + if (ime_selection.y != 0) { + caret_pos.y = _get_column_x_offset_for_line(caret.column + ime_selection.x + ime_selection.y, caret.line); + } else { + caret_pos.y = _get_column_x_offset_for_line(caret.column + ime_text.size(), caret.line); + } + } else { + caret_pos.y = caret_pos.x; + } + + if (MAX(caret_pos.x, caret_pos.y) > (caret.x_ofs + visible_width)) { + caret.x_ofs = MAX(caret_pos.x, caret_pos.y) - visible_width + 1; + } + + if (MIN(caret_pos.x, caret_pos.y) < caret.x_ofs) { + caret.x_ofs = MIN(caret_pos.x, caret_pos.y); + } + } else { + caret.x_ofs = 0; } + h_scroll->set_value(caret.x_ofs); + + update(); } -void TextEdit::_toggle_draw_caret() { - draw_caret = !draw_caret; - if (is_visible_in_tree() && has_focus() && window_has_focus) { - update(); +void TextEdit::center_viewport_to_caret() { + // Move viewport so the caret is in the center of the screen. + scrolling = false; + minimap_clicked = false; + + set_line_as_center_visible(caret.line, get_caret_wrap_index()); + int visible_width = get_size().width - style_normal->get_minimum_size().width - gutters_width - gutter_padding; + if (draw_minimap) { + visible_width -= minimap_width; } -} + if (v_scroll->is_visible_in_tree()) { + visible_width -= v_scroll->get_combined_minimum_size().width; + } + visible_width -= 20; // Give it a little more space. -void TextEdit::_update_caches() { - cache.style_normal = get_theme_stylebox("normal"); - cache.style_focus = get_theme_stylebox("focus"); - cache.style_readonly = get_theme_stylebox("read_only"); - cache.font = get_theme_font("font"); - cache.font_size = get_theme_font_size("font_size"); - cache.outline_color = get_theme_color("font_outline_color"); - cache.outline_size = get_theme_constant("outline_size"); - cache.caret_color = get_theme_color("caret_color"); - cache.caret_background_color = get_theme_color("caret_background_color"); - cache.font_color = get_theme_color("font_color"); - cache.font_selected_color = get_theme_color("font_selected_color"); - cache.font_readonly_color = get_theme_color("font_readonly_color"); - cache.selection_color = get_theme_color("selection_color"); - cache.current_line_color = get_theme_color("current_line_color"); - cache.line_length_guideline_color = get_theme_color("line_length_guideline_color"); - cache.code_folding_color = get_theme_color("code_folding_color", "CodeEdit"); - cache.brace_mismatch_color = get_theme_color("brace_mismatch_color"); - cache.word_highlighted_color = get_theme_color("word_highlighted_color"); - cache.search_result_color = get_theme_color("search_result_color"); - cache.search_result_border_color = get_theme_color("search_result_border_color"); - cache.background_color = get_theme_color("background_color"); -#ifdef TOOLS_ENABLED - cache.line_spacing = get_theme_constant("line_spacing") * EDSCALE; -#else - cache.line_spacing = get_theme_constant("line_spacing"); -#endif - cache.tab_icon = get_theme_icon("tab"); - cache.space_icon = get_theme_icon("space"); - cache.folded_eol_icon = get_theme_icon("folded_eol_icon", "CodeEdit"); + if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE) { + // Center x offset. - TextServer::Direction dir; - if (text_direction == Control::TEXT_DIRECTION_INHERITED) { - dir = is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR; + Vector2i caret_pos; + + // Get position of the start of caret. + if (ime_text.length() != 0 && ime_selection.x != 0) { + caret_pos.x = _get_column_x_offset_for_line(caret.column + ime_selection.x, caret.line); + } else { + caret_pos.x = _get_column_x_offset_for_line(caret.column, caret.line); + } + + // Get position of the end of caret. + if (ime_text.length() != 0) { + if (ime_selection.y != 0) { + caret_pos.y = _get_column_x_offset_for_line(caret.column + ime_selection.x + ime_selection.y, caret.line); + } else { + caret_pos.y = _get_column_x_offset_for_line(caret.column + ime_text.size(), caret.line); + } + } else { + caret_pos.y = caret_pos.x; + } + + if (MAX(caret_pos.x, caret_pos.y) > (caret.x_ofs + visible_width)) { + caret.x_ofs = MAX(caret_pos.x, caret_pos.y) - visible_width + 1; + } + + if (MIN(caret_pos.x, caret_pos.y) < caret.x_ofs) { + caret.x_ofs = MIN(caret_pos.x, caret_pos.y); + } } else { - dir = (TextServer::Direction)text_direction; + caret.x_ofs = 0; } - text.set_direction_and_language(dir, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); - text.set_font_features(opentype_features); - text.set_draw_control_chars(draw_control_chars); - text.set_font(cache.font); - text.set_font_size(cache.font_size); - text.invalidate_all(); + h_scroll->set_value(caret.x_ofs); - if (syntax_highlighter.is_valid()) { - syntax_highlighter->set_text_edit(this); + update(); +} + +/* Minimap */ +void TextEdit::set_draw_minimap(bool p_draw) { + if (draw_minimap != p_draw) { + draw_minimap = p_draw; + _update_wrap_at_column(); } + update(); } -/* Syntax Highlighting. */ -Ref<SyntaxHighlighter> TextEdit::get_syntax_highlighter() { - return syntax_highlighter; +bool TextEdit::is_drawing_minimap() const { + return draw_minimap; } -void TextEdit::set_syntax_highlighter(Ref<SyntaxHighlighter> p_syntax_highlighter) { - syntax_highlighter = p_syntax_highlighter; - if (syntax_highlighter.is_valid()) { - syntax_highlighter->set_text_edit(this); +void TextEdit::set_minimap_width(int p_minimap_width) { + if (minimap_width != p_minimap_width) { + minimap_width = p_minimap_width; + _update_wrap_at_column(); } update(); } -/* Gutters. */ -void TextEdit::_update_gutter_width() { - gutters_width = 0; - for (int i = 0; i < gutters.size(); i++) { - if (gutters[i].draw) { - gutters_width += gutters[i].width; - } - } - if (gutters_width > 0) { - gutter_padding = 2; - } - update(); +int TextEdit::get_minimap_width() const { + return minimap_width; } +int TextEdit::get_minimap_visible_lines() const { + return _get_control_height() / (minimap_char_size.y + minimap_line_spacing); +} + +/* Gutters. */ void TextEdit::add_gutter(int p_at) { if (p_at < 0 || p_at > gutters.size()) { gutters.push_back(GutterInfo()); @@ -4120,7 +4190,7 @@ void TextEdit::add_gutter(int p_at) { for (int i = 0; i < text.size() + 1; i++) { text.add_gutter(p_at); } - emit_signal("gutter_added"); + emit_signal(SNAME("gutter_added")); update(); } @@ -4132,7 +4202,7 @@ void TextEdit::remove_gutter(int p_gutter) { for (int i = 0; i < text.size() + 1; i++) { text.remove_gutter(p_gutter); } - emit_signal("gutter_removed"); + emit_signal(SNAME("gutter_removed")); update(); } @@ -4163,6 +4233,9 @@ TextEdit::GutterType TextEdit::get_gutter_type(int p_gutter) const { void TextEdit::set_gutter_width(int p_gutter, int p_width) { ERR_FAIL_INDEX(p_gutter, gutters.size()); + if (gutters[p_gutter].width == p_width) { + return; + } gutters.write[p_gutter].width = p_width; _update_gutter_width(); } @@ -4178,6 +4251,9 @@ int TextEdit::get_total_gutter_width() const { void TextEdit::set_gutter_draw(int p_gutter, bool p_draw) { ERR_FAIL_INDEX(p_gutter, gutters.size()); + if (gutters[p_gutter].draw == p_draw) { + return; + } gutters.write[p_gutter].draw = p_draw; _update_gutter_width(); } @@ -4276,7 +4352,7 @@ String TextEdit::get_line_gutter_text(int p_line, int p_gutter) const { return text.get_line_gutter_text(p_line, p_gutter); } -void TextEdit::set_line_gutter_icon(int p_line, int p_gutter, Ref<Texture2D> p_icon) { +void TextEdit::set_line_gutter_icon(int p_line, int p_gutter, const Ref<Texture2D> &p_icon) { ERR_FAIL_INDEX(p_line, text.size()); ERR_FAIL_INDEX(p_gutter, gutters.size()); text.set_line_gutter_icon(p_line, p_gutter, p_icon); @@ -4296,7 +4372,7 @@ void TextEdit::set_line_gutter_item_color(int p_line, int p_gutter, const Color update(); } -Color TextEdit::get_line_gutter_item_color(int p_line, int p_gutter) { +Color TextEdit::get_line_gutter_item_color(int p_line, int p_gutter) const { ERR_FAIL_INDEX_V(p_line, text.size(), Color()); ERR_FAIL_INDEX_V(p_gutter, gutters.size(), Color()); return text.get_line_gutter_item_color(p_line, p_gutter); @@ -4321,464 +4397,561 @@ void TextEdit::set_line_background_color(int p_line, const Color &p_color) { update(); } -Color TextEdit::get_line_background_color(int p_line) { +Color TextEdit::get_line_background_color(int p_line) const { ERR_FAIL_INDEX_V(p_line, text.size(), Color()); return text.get_line_background_color(p_line); } -void TextEdit::cut() { - if (readonly) { - return; +/* Syntax Highlighting. */ +void TextEdit::set_syntax_highlighter(Ref<SyntaxHighlighter> p_syntax_highlighter) { + syntax_highlighter = p_syntax_highlighter; + if (syntax_highlighter.is_valid()) { + syntax_highlighter->set_text_edit(this); } + update(); +} - if (!selection.active) { - String clipboard = text[cursor.line]; - DisplayServer::get_singleton()->clipboard_set(clipboard); - cursor_set_line(cursor.line); - cursor_set_column(0); +Ref<SyntaxHighlighter> TextEdit::get_syntax_highlighter() const { + return syntax_highlighter; +} - if (cursor.line == 0 && get_line_count() > 1) { - _remove_text(cursor.line, 0, cursor.line + 1, 0); - } else { - _remove_text(cursor.line, 0, cursor.line, text[cursor.line].length()); - backspace(); - cursor_set_line(cursor.line + 1); - } +/* Visual. */ +void TextEdit::set_highlight_current_line(bool p_enabled) { + highlight_current_line = p_enabled; + update(); +} - update(); - cut_copy_line = clipboard; +bool TextEdit::is_highlight_current_line_enabled() const { + return highlight_current_line; +} - } else { - String clipboard = _base_get_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column); - DisplayServer::get_singleton()->clipboard_set(clipboard); +void TextEdit::set_highlight_all_occurrences(const bool p_enabled) { + highlight_all_occurrences = p_enabled; + update(); +} - _remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column); - cursor_set_line(selection.from_line, false); // Set afterwards else it causes the view to be offset. - cursor_set_column(selection.from_column); +bool TextEdit::is_highlight_all_occurrences_enabled() const { + return highlight_all_occurrences; +} - selection.active = false; - selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE; +void TextEdit::set_draw_control_chars(bool p_draw_control_chars) { + if (draw_control_chars != p_draw_control_chars) { + draw_control_chars = p_draw_control_chars; + if (menu) { + menu->set_item_checked(menu->get_item_index(MENU_DISPLAY_UCC), draw_control_chars); + } + text.set_draw_control_chars(draw_control_chars); + text.invalidate_all(); update(); - cut_copy_line = ""; } } -void TextEdit::copy() { - if (!selection.active) { - if (text[cursor.line].length() != 0) { - String clipboard = _base_get_text(cursor.line, 0, cursor.line, text[cursor.line].length()); - DisplayServer::get_singleton()->clipboard_set(clipboard); - cut_copy_line = clipboard; - } - } else { - String clipboard = _base_get_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column); - DisplayServer::get_singleton()->clipboard_set(clipboard); - cut_copy_line = ""; - } +bool TextEdit::get_draw_control_chars() const { + return draw_control_chars; } -void TextEdit::paste() { - if (readonly) { - return; - } +void TextEdit::set_draw_tabs(bool p_draw) { + draw_tabs = p_draw; + update(); +} - String clipboard = DisplayServer::get_singleton()->clipboard_get(); +bool TextEdit::is_drawing_tabs() const { + return draw_tabs; +} - begin_complex_operation(); - if (selection.active) { - selection.active = false; - selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE; - _remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column); - cursor_set_line(selection.from_line, false); - cursor_set_column(selection.from_column); +void TextEdit::set_draw_spaces(bool p_draw) { + draw_spaces = p_draw; + update(); +} - } else if (!cut_copy_line.is_empty() && cut_copy_line == clipboard) { - cursor_set_column(0); - String ins = "\n"; - clipboard += ins; - } +bool TextEdit::is_drawing_spaces() const { + return draw_spaces; +} - _insert_text_at_cursor(clipboard); - end_complex_operation(); +void TextEdit::_bind_methods() { + /*Internal. */ - update(); -} + ClassDB::bind_method(D_METHOD("_text_changed_emit"), &TextEdit::_text_changed_emit); -void TextEdit::select_all() { - if (!selecting_enabled) { - return; - } + /* Text */ + // Text properties + ClassDB::bind_method(D_METHOD("has_ime_text"), &TextEdit::has_ime_text); - if (text.size() == 1 && text[0].length() == 0) { - return; - } - selection.active = true; - selection.from_line = 0; - selection.from_column = 0; - selection.selecting_line = 0; - selection.selecting_column = 0; - selection.to_line = text.size() - 1; - selection.to_column = text[selection.to_line].length(); - selection.selecting_mode = SelectionMode::SELECTION_MODE_SHIFT; - selection.shiftclick_left = true; - cursor_set_line(selection.to_line, false); - cursor_set_column(selection.to_column, false); - update(); -} + ClassDB::bind_method(D_METHOD("set_editable", "enable"), &TextEdit::set_editable); + ClassDB::bind_method(D_METHOD("is_editable"), &TextEdit::is_editable); -void TextEdit::select_word_under_caret() { - if (!selecting_enabled) { - return; - } + 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); - if (text.size() == 1 && text[0].length() == 0) { - return; - } + 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); - if (selection.active) { - // Allow toggling selection by pressing the shortcut a second time. - // This is also usable as a general-purpose "deselect" shortcut after - // selecting anything. - deselect(); - return; - } + ClassDB::bind_method(D_METHOD("set_language", "language"), &TextEdit::set_language); + ClassDB::bind_method(D_METHOD("get_language"), &TextEdit::get_language); - int begin = 0; - int end = 0; - const Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text.get_line_data(cursor.line)->get_rid()); - for (int i = 0; i < words.size(); i++) { - if (words[i].x <= cursor.column && words[i].y >= cursor.column) { - begin = words[i].x; - end = words[i].y; - break; - } - } + ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override", "parser"), &TextEdit::set_structured_text_bidi_override); + ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override"), &TextEdit::get_structured_text_bidi_override); + ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override_options", "args"), &TextEdit::set_structured_text_bidi_override_options); + ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override_options"), &TextEdit::get_structured_text_bidi_override_options); - select(cursor.line, begin, cursor.line, end); - // Move the cursor to the end of the word for easier editing. - cursor_set_column(end, false); -} + ClassDB::bind_method(D_METHOD("set_tab_size", "size"), &TextEdit::set_tab_size); + ClassDB::bind_method(D_METHOD("get_tab_size"), &TextEdit::get_tab_size); -void TextEdit::deselect() { - selection.active = false; - update(); -} + // User controls + ClassDB::bind_method(D_METHOD("set_overtype_mode_enabled", "enabled"), &TextEdit::set_overtype_mode_enabled); + ClassDB::bind_method(D_METHOD("is_overtype_mode_enabled"), &TextEdit::is_overtype_mode_enabled); -void TextEdit::select(int p_from_line, int p_from_column, int p_to_line, int p_to_column) { - if (!selecting_enabled) { - return; - } + ClassDB::bind_method(D_METHOD("set_context_menu_enabled", "enable"), &TextEdit::set_context_menu_enabled); + ClassDB::bind_method(D_METHOD("is_context_menu_enabled"), &TextEdit::is_context_menu_enabled); - if (p_from_line < 0) { - p_from_line = 0; - } else if (p_from_line >= text.size()) { - p_from_line = text.size() - 1; - } - if (p_from_column >= text[p_from_line].length()) { - p_from_column = text[p_from_line].length(); - } - if (p_from_column < 0) { - p_from_column = 0; - } + ClassDB::bind_method(D_METHOD("set_shortcut_keys_enabled", "enable"), &TextEdit::set_shortcut_keys_enabled); + ClassDB::bind_method(D_METHOD("is_shortcut_keys_enabled"), &TextEdit::is_shortcut_keys_enabled); - if (p_to_line < 0) { - p_to_line = 0; - } else if (p_to_line >= text.size()) { - p_to_line = text.size() - 1; - } - if (p_to_column >= text[p_to_line].length()) { - p_to_column = text[p_to_line].length(); - } - if (p_to_column < 0) { - p_to_column = 0; - } + ClassDB::bind_method(D_METHOD("set_virtual_keyboard_enabled", "enable"), &TextEdit::set_virtual_keyboard_enabled); + ClassDB::bind_method(D_METHOD("is_virtual_keyboard_enabled"), &TextEdit::is_virtual_keyboard_enabled); - selection.from_line = p_from_line; - selection.from_column = p_from_column; - selection.to_line = p_to_line; - selection.to_column = p_to_column; + // Text manipulation + ClassDB::bind_method(D_METHOD("clear"), &TextEdit::clear); - selection.active = true; + 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); - if (selection.from_line == selection.to_line) { - if (selection.from_column == selection.to_column) { - selection.active = false; + ClassDB::bind_method(D_METHOD("set_line", "line", "new_text"), &TextEdit::set_line); + ClassDB::bind_method(D_METHOD("get_line", "line"), &TextEdit::get_line); - } else if (selection.from_column > selection.to_column) { - selection.shiftclick_left = false; - SWAP(selection.from_column, selection.to_column); - } else { - selection.shiftclick_left = true; - } - } else if (selection.from_line > selection.to_line) { - selection.shiftclick_left = false; - SWAP(selection.from_line, selection.to_line); - SWAP(selection.from_column, selection.to_column); - } else { - selection.shiftclick_left = true; - } + ClassDB::bind_method(D_METHOD("get_line_width", "line", "wrap_index"), &TextEdit::get_line_width, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("get_line_height"), &TextEdit::get_line_height); - update(); -} + ClassDB::bind_method(D_METHOD("get_indent_level", "line"), &TextEdit::get_indent_level); + ClassDB::bind_method(D_METHOD("get_first_non_whitespace_column", "line"), &TextEdit::get_first_non_whitespace_column); -void TextEdit::swap_lines(int line1, int line2) { - String tmp = get_line(line1); - String tmp2 = get_line(line2); - set_line(line2, tmp); - set_line(line1, tmp2); -} + ClassDB::bind_method(D_METHOD("swap_lines", "from_line", "to_line"), &TextEdit::swap_lines); -bool TextEdit::is_selection_active() const { - return selection.active; -} + ClassDB::bind_method(D_METHOD("insert_line_at", "line", "text"), &TextEdit::insert_line_at); + ClassDB::bind_method(D_METHOD("insert_text_at_caret", "text"), &TextEdit::insert_text_at_caret); -int TextEdit::get_selection_from_line() const { - ERR_FAIL_COND_V(!selection.active, -1); - return selection.from_line; -} + ClassDB::bind_method(D_METHOD("remove_text", "from_line", "from_column", "to_line", "to_column"), &TextEdit::remove_text); -int TextEdit::get_selection_from_column() const { - ERR_FAIL_COND_V(!selection.active, -1); - return selection.from_column; -} + ClassDB::bind_method(D_METHOD("get_last_unhidden_line"), &TextEdit::get_last_unhidden_line); + ClassDB::bind_method(D_METHOD("get_next_visible_line_offset_from", "line", "visible_amount"), &TextEdit::get_next_visible_line_offset_from); + ClassDB::bind_method(D_METHOD("get_next_visible_line_index_offset_from", "line", "wrap_index", "visible_amount"), &TextEdit::get_next_visible_line_index_offset_from); -int TextEdit::get_selection_to_line() const { - ERR_FAIL_COND_V(!selection.active, -1); - return selection.to_line; -} + // Overridable actions + ClassDB::bind_method(D_METHOD("backspace"), &TextEdit::backspace); -int TextEdit::get_selection_to_column() const { - ERR_FAIL_COND_V(!selection.active, -1); - return selection.to_column; -} + ClassDB::bind_method(D_METHOD("cut"), &TextEdit::cut); + ClassDB::bind_method(D_METHOD("copy"), &TextEdit::copy); + ClassDB::bind_method(D_METHOD("paste"), &TextEdit::paste); -String TextEdit::get_selection_text() const { - if (!selection.active) { - return ""; - } + GDVIRTUAL_BIND(_handle_unicode_input, "unicode_char") + GDVIRTUAL_BIND(_backspace) + GDVIRTUAL_BIND(_cut) + GDVIRTUAL_BIND(_copy) + GDVIRTUAL_BIND(_paste) - return _base_get_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column); -} + // Context Menu + BIND_ENUM_CONSTANT(MENU_CUT); + BIND_ENUM_CONSTANT(MENU_COPY); + BIND_ENUM_CONSTANT(MENU_PASTE); + BIND_ENUM_CONSTANT(MENU_CLEAR); + BIND_ENUM_CONSTANT(MENU_SELECT_ALL); + BIND_ENUM_CONSTANT(MENU_UNDO); + BIND_ENUM_CONSTANT(MENU_REDO); + BIND_ENUM_CONSTANT(MENU_DIR_INHERITED); + BIND_ENUM_CONSTANT(MENU_DIR_AUTO); + BIND_ENUM_CONSTANT(MENU_DIR_LTR); + BIND_ENUM_CONSTANT(MENU_DIR_RTL); + BIND_ENUM_CONSTANT(MENU_DISPLAY_UCC); + BIND_ENUM_CONSTANT(MENU_INSERT_LRM); + BIND_ENUM_CONSTANT(MENU_INSERT_RLM); + BIND_ENUM_CONSTANT(MENU_INSERT_LRE); + BIND_ENUM_CONSTANT(MENU_INSERT_RLE); + BIND_ENUM_CONSTANT(MENU_INSERT_LRO); + BIND_ENUM_CONSTANT(MENU_INSERT_RLO); + BIND_ENUM_CONSTANT(MENU_INSERT_PDF); + BIND_ENUM_CONSTANT(MENU_INSERT_ALM); + BIND_ENUM_CONSTANT(MENU_INSERT_LRI); + BIND_ENUM_CONSTANT(MENU_INSERT_RLI); + BIND_ENUM_CONSTANT(MENU_INSERT_FSI); + BIND_ENUM_CONSTANT(MENU_INSERT_PDI); + BIND_ENUM_CONSTANT(MENU_INSERT_ZWJ); + BIND_ENUM_CONSTANT(MENU_INSERT_ZWNJ); + BIND_ENUM_CONSTANT(MENU_INSERT_WJ); + BIND_ENUM_CONSTANT(MENU_INSERT_SHY); + BIND_ENUM_CONSTANT(MENU_MAX); -String TextEdit::get_word_under_cursor() const { - Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text.get_line_data(cursor.line)->get_rid()); - for (int i = 0; i < words.size(); i++) { - if (words[i].x <= cursor.column && words[i].y > cursor.column) { - return text[cursor.line].substr(words[i].x, words[i].y - words[i].x); - } - } - return ""; -} + /* Versioning */ + ClassDB::bind_method(D_METHOD("begin_complex_operation"), &TextEdit::begin_complex_operation); + ClassDB::bind_method(D_METHOD("end_complex_operation"), &TextEdit::end_complex_operation); -void TextEdit::set_search_text(const String &p_search_text) { - search_text = p_search_text; -} + ClassDB::bind_method(D_METHOD("has_undo"), &TextEdit::has_undo); + ClassDB::bind_method(D_METHOD("has_redo"), &TextEdit::has_redo); + ClassDB::bind_method(D_METHOD("undo"), &TextEdit::undo); + ClassDB::bind_method(D_METHOD("redo"), &TextEdit::redo); + ClassDB::bind_method(D_METHOD("clear_undo_history"), &TextEdit::clear_undo_history); -void TextEdit::set_search_flags(uint32_t p_flags) { - search_flags = p_flags; -} + ClassDB::bind_method(D_METHOD("tag_saved_version"), &TextEdit::tag_saved_version); -void TextEdit::set_current_search_result(int line, int col) { - search_result_line = line; - search_result_col = col; - update(); -} + ClassDB::bind_method(D_METHOD("get_version"), &TextEdit::get_version); + ClassDB::bind_method(D_METHOD("get_saved_version"), &TextEdit::get_saved_version); -void TextEdit::set_highlight_all_occurrences(const bool p_enabled) { - highlight_all_occurrences = p_enabled; - update(); -} + /* Search */ + BIND_ENUM_CONSTANT(SEARCH_MATCH_CASE); + BIND_ENUM_CONSTANT(SEARCH_WHOLE_WORDS); + BIND_ENUM_CONSTANT(SEARCH_BACKWARDS); -bool TextEdit::is_highlight_all_occurrences_enabled() const { - return highlight_all_occurrences; -} + ClassDB::bind_method(D_METHOD("set_search_text", "search_text"), &TextEdit::set_search_text); + ClassDB::bind_method(D_METHOD("set_search_flags", "flags"), &TextEdit::set_search_flags); -int TextEdit::_get_column_pos_of_word(const String &p_key, const String &p_search, uint32_t p_search_flags, int p_from_column) { - int col = -1; + ClassDB::bind_method(D_METHOD("search", "text", "flags", "from_line", "from_colum"), &TextEdit::search); - if (p_key.length() > 0 && p_search.length() > 0) { - if (p_from_column < 0 || p_from_column > p_search.length()) { - p_from_column = 0; - } + /* Tooltip */ + ClassDB::bind_method(D_METHOD("set_tooltip_request_func", "object", "callback", "data"), &TextEdit::set_tooltip_request_func); - while (col == -1 && p_from_column <= p_search.length()) { - if (p_search_flags & SEARCH_MATCH_CASE) { - col = p_search.find(p_key, p_from_column); - } else { - col = p_search.findn(p_key, p_from_column); - } + /* Mouse */ + ClassDB::bind_method(D_METHOD("get_local_mouse_pos"), &TextEdit::get_local_mouse_pos); - // Whole words only. - if (col != -1 && p_search_flags & SEARCH_WHOLE_WORDS) { - p_from_column = col; + ClassDB::bind_method(D_METHOD("get_word_at_pos", "position"), &TextEdit::get_word_at_pos); - if (col > 0 && _is_text_char(p_search[col - 1])) { - col = -1; - } else if ((col + p_key.length()) < p_search.length() && _is_text_char(p_search[col + p_key.length()])) { - col = -1; - } - } + ClassDB::bind_method(D_METHOD("get_line_column_at_pos", "position"), &TextEdit::get_line_column_at_pos); + ClassDB::bind_method(D_METHOD("get_minimap_line_at_pos", "position"), &TextEdit::get_minimap_line_at_pos); - p_from_column += 1; - } - } - return col; -} + ClassDB::bind_method(D_METHOD("is_dragging_cursor"), &TextEdit::is_dragging_cursor); -Dictionary TextEdit::_search_bind(const String &p_key, uint32_t p_search_flags, int p_from_line, int p_from_column) const { - int col, line; - if (search(p_key, p_search_flags, p_from_line, p_from_column, line, col)) { - Dictionary result; - result["line"] = line; - result["column"] = col; - return result; + /* Caret. */ + BIND_ENUM_CONSTANT(CARET_TYPE_LINE); + BIND_ENUM_CONSTANT(CARET_TYPE_BLOCK); - } else { - return Dictionary(); - } -} + // internal. + ClassDB::bind_method(D_METHOD("_emit_caret_changed"), &TextEdit::_emit_caret_changed); -bool TextEdit::search(const String &p_key, uint32_t p_search_flags, int p_from_line, int p_from_column, int &r_line, int &r_column) const { - if (p_key.length() == 0) { - return false; - } - ERR_FAIL_INDEX_V(p_from_line, text.size(), false); - ERR_FAIL_INDEX_V(p_from_column, text[p_from_line].length() + 1, false); + ClassDB::bind_method(D_METHOD("set_caret_type", "type"), &TextEdit::set_caret_type); + ClassDB::bind_method(D_METHOD("get_caret_type"), &TextEdit::get_caret_type); - // Search through the whole document, but start by current line. + ClassDB::bind_method(D_METHOD("set_caret_blink_enabled", "enable"), &TextEdit::set_caret_blink_enabled); + ClassDB::bind_method(D_METHOD("is_caret_blink_enabled"), &TextEdit::is_caret_blink_enabled); - int line = p_from_line; - int pos = -1; + ClassDB::bind_method(D_METHOD("set_caret_blink_speed", "blink_speed"), &TextEdit::set_caret_blink_speed); + ClassDB::bind_method(D_METHOD("get_caret_blink_speed"), &TextEdit::get_caret_blink_speed); - for (int i = 0; i < text.size() + 1; i++) { - if (line < 0) { - line = text.size() - 1; - } - if (line == text.size()) { - line = 0; - } + ClassDB::bind_method(D_METHOD("set_move_caret_on_right_click_enabled", "enable"), &TextEdit::set_move_caret_on_right_click_enabled); + ClassDB::bind_method(D_METHOD("is_move_caret_on_right_click_enabled"), &TextEdit::is_move_caret_on_right_click_enabled); - String text_line = text[line]; - int from_column = 0; - if (line == p_from_line) { - if (i == text.size()) { - // Wrapped. + ClassDB::bind_method(D_METHOD("set_caret_mid_grapheme_enabled", "enabled"), &TextEdit::set_caret_mid_grapheme_enabled); + ClassDB::bind_method(D_METHOD("is_caret_mid_grapheme_enabled"), &TextEdit::is_caret_mid_grapheme_enabled); - if (p_search_flags & SEARCH_BACKWARDS) { - from_column = text_line.length(); - } else { - from_column = 0; - } + ClassDB::bind_method(D_METHOD("is_caret_visible"), &TextEdit::is_caret_visible); + ClassDB::bind_method(D_METHOD("get_caret_draw_pos"), &TextEdit::get_caret_draw_pos); - } else { - from_column = p_from_column; - } + ClassDB::bind_method(D_METHOD("set_caret_line", "line", "adjust_viewport", "can_be_hidden", "wrap_index"), &TextEdit::set_caret_line, DEFVAL(true), DEFVAL(true), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_caret_line"), &TextEdit::get_caret_line); - } else { - if (p_search_flags & SEARCH_BACKWARDS) { - from_column = text_line.length() - 1; - } else { - from_column = 0; - } - } + ClassDB::bind_method(D_METHOD("set_caret_column", "column", "adjust_viewport"), &TextEdit::set_caret_column, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("get_caret_column"), &TextEdit::get_caret_column); - pos = -1; + ClassDB::bind_method(D_METHOD("get_caret_wrap_index"), &TextEdit::get_caret_wrap_index); - int pos_from = (p_search_flags & SEARCH_BACKWARDS) ? text_line.length() : 0; - int last_pos = -1; + ClassDB::bind_method(D_METHOD("get_word_under_caret"), &TextEdit::get_word_under_caret); - while (true) { - if (p_search_flags & SEARCH_BACKWARDS) { - while ((last_pos = (p_search_flags & SEARCH_MATCH_CASE) ? text_line.rfind(p_key, pos_from) : text_line.rfindn(p_key, pos_from)) != -1) { - if (last_pos <= from_column) { - pos = last_pos; - break; - } - pos_from = last_pos - p_key.length(); - if (pos_from < 0) { - break; - } - } - } else { - while ((last_pos = (p_search_flags & SEARCH_MATCH_CASE) ? text_line.find(p_key, pos_from) : text_line.findn(p_key, pos_from)) != -1) { - if (last_pos >= from_column) { - pos = last_pos; - break; - } - pos_from = last_pos + p_key.length(); - } - } + /* Selection. */ + BIND_ENUM_CONSTANT(SELECTION_MODE_NONE); + BIND_ENUM_CONSTANT(SELECTION_MODE_SHIFT); + BIND_ENUM_CONSTANT(SELECTION_MODE_POINTER); + BIND_ENUM_CONSTANT(SELECTION_MODE_WORD); + BIND_ENUM_CONSTANT(SELECTION_MODE_LINE); - bool is_match = true; + ClassDB::bind_method(D_METHOD("set_selecting_enabled", "enable"), &TextEdit::set_selecting_enabled); + ClassDB::bind_method(D_METHOD("is_selecting_enabled"), &TextEdit::is_selecting_enabled); - if (pos != -1 && (p_search_flags & SEARCH_WHOLE_WORDS)) { - // Validate for whole words. - if (pos > 0 && _is_text_char(text_line[pos - 1])) { - is_match = false; - } else if (pos + p_key.length() < text_line.length() && _is_text_char(text_line[pos + p_key.length()])) { - is_match = false; - } - } + 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); - if (pos_from == -1) { - pos = -1; - } + ClassDB::bind_method(D_METHOD("set_selection_mode", "mode", "line", "column"), &TextEdit::set_selection_mode, DEFVAL(-1), DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("get_selection_mode"), &TextEdit::get_selection_mode); - if (is_match || last_pos == -1 || pos == -1) { - break; - } + ClassDB::bind_method(D_METHOD("select_all"), &TextEdit::select_all); + ClassDB::bind_method(D_METHOD("select_word_under_caret"), &TextEdit::select_word_under_caret); + ClassDB::bind_method(D_METHOD("select", "from_line", "from_column", "to_line", "to_column"), &TextEdit::select); - pos_from = (p_search_flags & SEARCH_BACKWARDS) ? pos - 1 : pos + 1; - pos = -1; - } + ClassDB::bind_method(D_METHOD("has_selection"), &TextEdit::has_selection); - if (pos != -1) { - break; - } + ClassDB::bind_method(D_METHOD("get_selected_text"), &TextEdit::get_selected_text); - if (p_search_flags & SEARCH_BACKWARDS) { - line--; + ClassDB::bind_method(D_METHOD("get_selection_line"), &TextEdit::get_selection_line); + ClassDB::bind_method(D_METHOD("get_selection_column"), &TextEdit::get_selection_column); + + ClassDB::bind_method(D_METHOD("get_selection_from_line"), &TextEdit::get_selection_from_line); + ClassDB::bind_method(D_METHOD("get_selection_from_column"), &TextEdit::get_selection_from_column); + ClassDB::bind_method(D_METHOD("get_selection_to_line"), &TextEdit::get_selection_to_line); + ClassDB::bind_method(D_METHOD("get_selection_to_column"), &TextEdit::get_selection_to_column); + + ClassDB::bind_method(D_METHOD("deselect"), &TextEdit::deselect); + ClassDB::bind_method(D_METHOD("delete_selection"), &TextEdit::delete_selection); + + /* line wrapping. */ + BIND_ENUM_CONSTANT(LINE_WRAPPING_NONE); + BIND_ENUM_CONSTANT(LINE_WRAPPING_BOUNDARY); + + // internal. + ClassDB::bind_method(D_METHOD("_update_wrap_at_column", "force"), &TextEdit::_update_wrap_at_column, DEFVAL(false)); + + ClassDB::bind_method(D_METHOD("set_line_wrapping_mode", "mode"), &TextEdit::set_line_wrapping_mode); + ClassDB::bind_method(D_METHOD("get_line_wrapping_mode"), &TextEdit::get_line_wrapping_mode); + + ClassDB::bind_method(D_METHOD("is_line_wrapped", "line"), &TextEdit::is_line_wrapped); + ClassDB::bind_method(D_METHOD("get_line_wrap_count", "line"), &TextEdit::get_line_wrap_count); + ClassDB::bind_method(D_METHOD("get_line_wrap_index_at_column", "line", "column"), &TextEdit::get_line_wrap_index_at_column); + + ClassDB::bind_method(D_METHOD("get_line_wrapped_text", "line"), &TextEdit::get_line_wrapped_text); + + /* Viewport. */ + // Scolling. + ClassDB::bind_method(D_METHOD("set_smooth_scroll_enable", "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); + ClassDB::bind_method(D_METHOD("get_v_scroll"), &TextEdit::get_v_scroll); + + ClassDB::bind_method(D_METHOD("set_h_scroll", "value"), &TextEdit::set_h_scroll); + ClassDB::bind_method(D_METHOD("get_h_scroll"), &TextEdit::get_h_scroll); + + ClassDB::bind_method(D_METHOD("set_scroll_past_end_of_file_enabled", "enable"), &TextEdit::set_scroll_past_end_of_file_enabled); + ClassDB::bind_method(D_METHOD("is_scroll_past_end_of_file_enabled"), &TextEdit::is_scroll_past_end_of_file_enabled); + + 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("get_scroll_pos_for_line", "line", "wrap_index"), &TextEdit::get_scroll_pos_for_line, DEFVAL(0)); + + // Visible lines. + ClassDB::bind_method(D_METHOD("set_line_as_first_visible", "line", "wrap_index"), &TextEdit::set_line_as_first_visible, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_first_visible_line"), &TextEdit::get_first_visible_line); + + ClassDB::bind_method(D_METHOD("set_line_as_center_visible", "line", "wrap_index"), &TextEdit::set_line_as_center_visible, DEFVAL(0)); + + ClassDB::bind_method(D_METHOD("set_line_as_last_visible", "line", "wrap_index"), &TextEdit::set_line_as_last_visible, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_last_full_visible_line"), &TextEdit::get_last_full_visible_line); + ClassDB::bind_method(D_METHOD("get_last_full_visible_line_wrap_index"), &TextEdit::get_last_full_visible_line_wrap_index); + + ClassDB::bind_method(D_METHOD("get_visible_line_count"), &TextEdit::get_visible_line_count); + ClassDB::bind_method(D_METHOD("get_total_visible_line_count"), &TextEdit::get_total_visible_line_count); + + // Auto adjust + ClassDB::bind_method(D_METHOD("adjust_viewport_to_caret"), &TextEdit::adjust_viewport_to_caret); + ClassDB::bind_method(D_METHOD("center_viewport_to_caret"), &TextEdit::center_viewport_to_caret); + + // Minimap + ClassDB::bind_method(D_METHOD("draw_minimap", "draw"), &TextEdit::set_draw_minimap); + ClassDB::bind_method(D_METHOD("is_drawing_minimap"), &TextEdit::is_drawing_minimap); + + ClassDB::bind_method(D_METHOD("set_minimap_width", "width"), &TextEdit::set_minimap_width); + ClassDB::bind_method(D_METHOD("get_minimap_width"), &TextEdit::get_minimap_width); + + ClassDB::bind_method(D_METHOD("get_minimap_visible_lines"), &TextEdit::get_minimap_visible_lines); + + /* Gutters. */ + BIND_ENUM_CONSTANT(GUTTER_TYPE_STRING); + BIND_ENUM_CONSTANT(GUTTER_TYPE_ICON); + BIND_ENUM_CONSTANT(GUTTER_TYPE_CUSTOM); + + ClassDB::bind_method(D_METHOD("add_gutter", "at"), &TextEdit::add_gutter, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("remove_gutter", "gutter"), &TextEdit::remove_gutter); + ClassDB::bind_method(D_METHOD("get_gutter_count"), &TextEdit::get_gutter_count); + ClassDB::bind_method(D_METHOD("set_gutter_name", "gutter", "name"), &TextEdit::set_gutter_name); + ClassDB::bind_method(D_METHOD("get_gutter_name", "gutter"), &TextEdit::get_gutter_name); + ClassDB::bind_method(D_METHOD("set_gutter_type", "gutter", "type"), &TextEdit::set_gutter_type); + ClassDB::bind_method(D_METHOD("get_gutter_type", "gutter"), &TextEdit::get_gutter_type); + ClassDB::bind_method(D_METHOD("set_gutter_width", "gutter", "width"), &TextEdit::set_gutter_width); + ClassDB::bind_method(D_METHOD("get_gutter_width", "gutter"), &TextEdit::get_gutter_width); + ClassDB::bind_method(D_METHOD("set_gutter_draw", "gutter", "draw"), &TextEdit::set_gutter_draw); + ClassDB::bind_method(D_METHOD("is_gutter_drawn", "gutter"), &TextEdit::is_gutter_drawn); + ClassDB::bind_method(D_METHOD("set_gutter_clickable", "gutter", "clickable"), &TextEdit::set_gutter_clickable); + ClassDB::bind_method(D_METHOD("is_gutter_clickable", "gutter"), &TextEdit::is_gutter_clickable); + ClassDB::bind_method(D_METHOD("set_gutter_overwritable", "gutter", "overwritable"), &TextEdit::set_gutter_overwritable); + ClassDB::bind_method(D_METHOD("is_gutter_overwritable", "gutter"), &TextEdit::is_gutter_overwritable); + ClassDB::bind_method(D_METHOD("merge_gutters", "from_line", "to_line"), &TextEdit::merge_gutters); + ClassDB::bind_method(D_METHOD("set_gutter_custom_draw", "column", "object", "callback"), &TextEdit::set_gutter_custom_draw); + ClassDB::bind_method(D_METHOD("get_total_gutter_width"), &TextEdit::get_total_gutter_width); + + // Line gutters. + ClassDB::bind_method(D_METHOD("set_line_gutter_metadata", "line", "gutter", "metadata"), &TextEdit::set_line_gutter_metadata); + ClassDB::bind_method(D_METHOD("get_line_gutter_metadata", "line", "gutter"), &TextEdit::get_line_gutter_metadata); + ClassDB::bind_method(D_METHOD("set_line_gutter_text", "line", "gutter", "text"), &TextEdit::set_line_gutter_text); + ClassDB::bind_method(D_METHOD("get_line_gutter_text", "line", "gutter"), &TextEdit::get_line_gutter_text); + ClassDB::bind_method(D_METHOD("set_line_gutter_icon", "line", "gutter", "icon"), &TextEdit::set_line_gutter_icon); + ClassDB::bind_method(D_METHOD("get_line_gutter_icon", "line", "gutter"), &TextEdit::get_line_gutter_icon); + ClassDB::bind_method(D_METHOD("set_line_gutter_item_color", "line", "gutter", "color"), &TextEdit::set_line_gutter_item_color); + ClassDB::bind_method(D_METHOD("get_line_gutter_item_color", "line", "gutter"), &TextEdit::get_line_gutter_item_color); + ClassDB::bind_method(D_METHOD("set_line_gutter_clickable", "line", "gutter", "clickable"), &TextEdit::set_line_gutter_clickable); + ClassDB::bind_method(D_METHOD("is_line_gutter_clickable", "line", "gutter"), &TextEdit::is_line_gutter_clickable); + + // Line style + ClassDB::bind_method(D_METHOD("set_line_background_color", "line", "color"), &TextEdit::set_line_background_color); + ClassDB::bind_method(D_METHOD("get_line_background_color", "line"), &TextEdit::get_line_background_color); + + /* Syntax Highlighting. */ + ClassDB::bind_method(D_METHOD("set_syntax_highlighter", "syntax_highlighter"), &TextEdit::set_syntax_highlighter); + ClassDB::bind_method(D_METHOD("get_syntax_highlighter"), &TextEdit::get_syntax_highlighter); + + /* Visual. */ + ClassDB::bind_method(D_METHOD("set_highlight_current_line", "enabled"), &TextEdit::set_highlight_current_line); + ClassDB::bind_method(D_METHOD("is_highlight_current_line_enabled"), &TextEdit::is_highlight_current_line_enabled); + + ClassDB::bind_method(D_METHOD("set_highlight_all_occurrences", "enable"), &TextEdit::set_highlight_all_occurrences); + ClassDB::bind_method(D_METHOD("is_highlight_all_occurrences_enabled"), &TextEdit::is_highlight_all_occurrences_enabled); + + ClassDB::bind_method(D_METHOD("get_draw_control_chars"), &TextEdit::get_draw_control_chars); + ClassDB::bind_method(D_METHOD("set_draw_control_chars", "enable"), &TextEdit::set_draw_control_chars); + + ClassDB::bind_method(D_METHOD("set_draw_tabs"), &TextEdit::set_draw_tabs); + ClassDB::bind_method(D_METHOD("is_drawing_tabs"), &TextEdit::is_drawing_tabs); + + ClassDB::bind_method(D_METHOD("set_draw_spaces"), &TextEdit::set_draw_spaces); + ClassDB::bind_method(D_METHOD("is_drawing_spaces"), &TextEdit::is_drawing_spaces); + + ClassDB::bind_method(D_METHOD("get_menu"), &TextEdit::get_menu); + ClassDB::bind_method(D_METHOD("is_menu_visible"), &TextEdit::is_menu_visible); + ClassDB::bind_method(D_METHOD("menu_option", "option"), &TextEdit::menu_option); + + /* Inspector */ + ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "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"), "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, "virtual_keyboard_enabled"), "set_virtual_keyboard_enabled", "is_virtual_keyboard_enabled"); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "wrap_mode", PROPERTY_HINT_ENUM, "None,Boundary"), "set_line_wrapping_mode", "get_line_wrapping_mode"); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_selected_font_color"), "set_override_selected_font_color", "is_overriding_selected_font_color"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_all_occurrences"), "set_highlight_all_occurrences", "is_highlight_all_occurrences_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_current_line"), "set_highlight_current_line", "is_highlight_current_line_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_tabs"), "set_draw_tabs", "is_drawing_tabs"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_spaces"), "set_draw_spaces", "is_drawing_spaces"); + + 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_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_GROUP("Minimap", "minimap_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "minimap_draw"), "draw_minimap", "is_drawing_minimap"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "minimap_width"), "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::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_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"); + + /* Signals */ + /* Core. */ + ADD_SIGNAL(MethodInfo("text_set")); + ADD_SIGNAL(MethodInfo("text_changed")); + ADD_SIGNAL(MethodInfo("lines_edited_from", PropertyInfo(Variant::INT, "from_line"), PropertyInfo(Variant::INT, "to_line"))); + + /* Caret. */ + ADD_SIGNAL(MethodInfo("caret_changed")); + + /* Gutters. */ + ADD_SIGNAL(MethodInfo("gutter_clicked", PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::INT, "gutter"))); + ADD_SIGNAL(MethodInfo("gutter_added")); + ADD_SIGNAL(MethodInfo("gutter_removed")); + + /* Settings. */ + GLOBAL_DEF("gui/timers/text_edit_idle_detect_sec", 3); + ProjectSettings::get_singleton()->set_custom_property_info("gui/timers/text_edit_idle_detect_sec", PropertyInfo(Variant::FLOAT, "gui/timers/text_edit_idle_detect_sec", PROPERTY_HINT_RANGE, "0,10,0.01,or_greater")); // No negative numbers. + GLOBAL_DEF("gui/common/text_edit_undo_stack_max_size", 1024); + 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); + double value = p_value; + if (value == -1) { + if (opentype_features.has(tag)) { + opentype_features.erase(tag); + text.set_font_features(opentype_features); + text.invalidate_all(); + update(); + } } else { - line++; + if ((double)opentype_features[tag] != value) { + opentype_features[tag] = value; + text.set_font_features(opentype_features); + text.invalidate_all(); + update(); + } } + notify_property_list_changed(); + return true; } - if (pos == -1) { - r_line = -1; - r_column = -1; - return false; - } - - r_line = line; - r_column = pos; - - return true; + return false; } -void TextEdit::_cursor_changed_emit() { - emit_signal("cursor_changed"); - cursor_changed_dirty = 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::_text_changed_emit() { - emit_signal("text_changed"); - text_changed_dirty = 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::FLOAT, "opentype_features/" + name)); + } + p_list->push_back(PropertyInfo(Variant::NIL, "opentype_features/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); } -void TextEdit::set_line_as_hidden(int p_line, bool p_hidden) { - ERR_FAIL_INDEX(p_line, text.size()); - if (is_hiding_enabled() || !p_hidden) { - text.set_hidden(p_line, p_hidden); +/* Internal API for CodeEdit. */ +// Line hiding. +void TextEdit::_set_hiding_enabled(bool p_enabled) { + if (!p_enabled) { + _unhide_all_lines(); } + hiding_enabled = p_enabled; update(); } -bool TextEdit::is_line_hidden(int p_line) const { +bool TextEdit::_is_hiding_enabled() const { + return hiding_enabled; +} + +bool TextEdit::_is_line_hidden(int p_line) const { ERR_FAIL_INDEX_V(p_line, text.size(), false); return text.is_hidden(p_line); } -void TextEdit::unhide_all_lines() { +void TextEdit::_unhide_all_lines() { for (int i = 0; i < text.size(); i++) { text.set_hidden(i, false); } @@ -4786,286 +4959,256 @@ void TextEdit::unhide_all_lines() { update(); } -int TextEdit::num_lines_from(int p_line_from, int visible_amount) const { - // Returns the number of lines (hidden and unhidden) from p_line_from to (p_line_from + visible_amount of unhidden lines). - ERR_FAIL_INDEX_V(p_line_from, text.size(), ABS(visible_amount)); - - if (!is_hiding_enabled()) { - return ABS(visible_amount); +void TextEdit::_set_line_as_hidden(int p_line, bool p_hidden) { + ERR_FAIL_INDEX(p_line, text.size()); + if (_is_hiding_enabled() || !p_hidden) { + text.set_hidden(p_line, p_hidden); } + update(); +} - int num_visible = 0; - int num_total = 0; - if (visible_amount >= 0) { - for (int i = p_line_from; i < text.size(); i++) { - num_total++; - if (!is_line_hidden(i)) { - num_visible++; - } - if (num_visible >= visible_amount) { - break; - } - } - } else { - visible_amount = ABS(visible_amount); - for (int i = p_line_from; i >= 0; i--) { - num_total++; - if (!is_line_hidden(i)) { - num_visible++; - } - if (num_visible >= visible_amount) { - break; - } - } - } - return num_total; +// Symbol lookup. +void TextEdit::_set_symbol_lookup_word(const String &p_symbol) { + lookup_symbol_word = p_symbol; + update(); } -int TextEdit::num_lines_from_rows(int p_line_from, int p_wrap_index_from, int visible_amount, int &wrap_index) const { - // Returns the number of lines (hidden and unhidden) from (p_line_from + p_wrap_index_from) row to (p_line_from + visible_amount of unhidden and wrapped rows). - // Wrap index is set to the wrap index of the last line. - wrap_index = 0; - ERR_FAIL_INDEX_V(p_line_from, text.size(), ABS(visible_amount)); +/* Text manipulation */ - if (!is_hiding_enabled() && !is_wrap_enabled()) { - return ABS(visible_amount); +// Overridable actions +void TextEdit::_handle_unicode_input_internal(const uint32_t p_unicode) { + if (!editable) { + return; } - int num_visible = 0; - int num_total = 0; - if (visible_amount == 0) { - num_total = 0; - wrap_index = 0; - } else if (visible_amount > 0) { - int i; - num_visible -= p_wrap_index_from; - for (i = p_line_from; i < text.size(); i++) { - num_total++; - if (!is_line_hidden(i)) { - num_visible++; - num_visible += times_line_wraps(i); - } - if (num_visible >= visible_amount) { - break; - } - } - wrap_index = times_line_wraps(MIN(i, text.size() - 1)) - MAX(0, num_visible - visible_amount); - } else { - visible_amount = ABS(visible_amount); - int i; - num_visible -= times_line_wraps(p_line_from) - p_wrap_index_from; - for (i = p_line_from; i >= 0; i--) { - num_total++; - if (!is_line_hidden(i)) { - num_visible++; - num_visible += times_line_wraps(i); - } - if (num_visible >= visible_amount) { - break; - } - } - wrap_index = MAX(0, num_visible - visible_amount); + bool had_selection = has_selection(); + if (had_selection) { + begin_complex_operation(); + delete_selection(); } - wrap_index = MAX(wrap_index, 0); - return num_total; -} -int TextEdit::get_last_unhidden_line() const { - // Returns the last line in the text that is not hidden. - if (!is_hiding_enabled()) { - return text.size() - 1; - } + /* Remove the old character if in insert mode and no selection. */ + if (overtype_mode && !had_selection) { + begin_complex_operation(); - int last_line; - for (last_line = text.size() - 1; last_line > 0; last_line--) { - if (!is_line_hidden(last_line)) { - break; + /* Make sure we don't try and remove empty space. */ + int cl = get_caret_line(); + int cc = get_caret_column(); + if (cc < get_line(cl).length()) { + _remove_text(cl, cc, cl, cc + 1); } } - return last_line; -} -int TextEdit::get_indent_level(int p_line) const { - ERR_FAIL_INDEX_V(p_line, text.size(), 0); + const char32_t chr[2] = { (char32_t)p_unicode, 0 }; + insert_text_at_caret(chr); - int tab_count = 0; - int whitespace_count = 0; - int line_length = text[p_line].size(); - for (int i = 0; i < line_length - 1; i++) { - if (text[p_line][i] == '\t') { - tab_count++; - } else if (text[p_line][i] == ' ') { - whitespace_count++; - } else { - break; - } + if ((overtype_mode && !had_selection) || (had_selection)) { + end_complex_operation(); } - return tab_count * text.get_tab_size() + whitespace_count; } -int TextEdit::get_first_non_whitespace_column(int p_line) const { - ERR_FAIL_INDEX_V(p_line, text.size(), 0); - - int col = 0; - while (col < text[p_line].length() && _is_whitespace(text[p_line][col])) { - col++; +void TextEdit::_backspace_internal() { + if (!editable) { + return; } - return col; -} - -int TextEdit::get_line_count() const { - return text.size(); -} - -int TextEdit::get_line_width(int p_line, int p_wrap_offset) const { - return text.get_line_width(p_line, p_wrap_offset); -} -void TextEdit::_do_text_op(const TextOperation &p_op, bool p_reverse) { - ERR_FAIL_COND(p_op.type == TextOperation::TYPE_NONE); - - bool insert = p_op.type == TextOperation::TYPE_INSERT; - if (p_reverse) { - insert = !insert; + if (has_selection()) { + delete_selection(); + return; } - if (insert) { - int check_line; - int check_column; - _base_insert_text(p_op.from_line, p_op.from_column, p_op.text, check_line, check_column); - ERR_FAIL_COND(check_line != p_op.to_line); // BUG. - ERR_FAIL_COND(check_column != p_op.to_column); // BUG. - } else { - _base_remove_text(p_op.from_line, p_op.from_column, p_op.to_line, p_op.to_column); - } -} + int cc = get_caret_column(); + int cl = get_caret_line(); -void TextEdit::_clear_redo() { - if (undo_stack_pos == nullptr) { - return; // Nothing to clear. + if (cc == 0 && cl == 0) { + return; } - _push_current_op(); + int prev_line = cc ? cl : cl - 1; + int prev_column = cc ? (cc - 1) : (text[cl - 1].length()); - while (undo_stack_pos) { - List<TextOperation>::Element *elem = undo_stack_pos; - undo_stack_pos = undo_stack_pos->next(); - undo_stack.erase(elem); + merge_gutters(prev_line, cl); + + if (_is_line_hidden(cl)) { + _set_line_as_hidden(prev_line, true); } + _remove_text(prev_line, prev_column, cl, cc); + + set_caret_line(prev_line, false, true); + set_caret_column(prev_column); } -void TextEdit::undo() { - if (readonly) { +void TextEdit::_cut_internal() { + if (!editable) { return; } - _push_current_op(); + if (has_selection()) { + DisplayServer::get_singleton()->clipboard_set(get_selected_text()); + delete_selection(); + cut_copy_line = ""; + return; + } - if (undo_stack_pos == nullptr) { - if (!undo_stack.size()) { - return; // Nothing to undo. - } + int cl = get_caret_line(); - undo_stack_pos = undo_stack.back(); + String clipboard = text[cl]; + DisplayServer::get_singleton()->clipboard_set(clipboard); + set_caret_line(cl); + set_caret_column(0); - } else if (undo_stack_pos == undo_stack.front()) { - return; // At the bottom of the undo stack. + if (cl == 0 && get_line_count() > 1) { + _remove_text(cl, 0, cl + 1, 0); } else { - undo_stack_pos = undo_stack_pos->prev(); + _remove_text(cl, 0, cl, text[cl].length()); + backspace(); + set_caret_line(get_caret_line() + 1); } - deselect(); - - TextOperation op = undo_stack_pos->get(); - _do_text_op(op, true); - if (op.type != TextOperation::TYPE_INSERT && (op.from_line != op.to_line || op.to_column != op.from_column + 1)) { - select(op.from_line, op.from_column, op.to_line, op.to_column); - } + cut_copy_line = clipboard; +} - current_op.version = op.prev_version; - if (undo_stack_pos->get().chain_backward) { - while (true) { - ERR_BREAK(!undo_stack_pos->prev()); - undo_stack_pos = undo_stack_pos->prev(); - op = undo_stack_pos->get(); - _do_text_op(op, true); - current_op.version = op.prev_version; - if (undo_stack_pos->get().chain_forward) { - break; - } - } +void TextEdit::_copy_internal() { + if (has_selection()) { + DisplayServer::get_singleton()->clipboard_set(get_selected_text()); + cut_copy_line = ""; + return; } - _update_scrollbars(); - if (undo_stack_pos->get().type == TextOperation::TYPE_REMOVE) { - cursor_set_line(undo_stack_pos->get().to_line, false); - cursor_set_column(undo_stack_pos->get().to_column); - } else { - cursor_set_line(undo_stack_pos->get().from_line, false); - cursor_set_column(undo_stack_pos->get().from_column); + int cl = get_caret_line(); + if (text[cl].length() != 0) { + String clipboard = _base_get_text(cl, 0, cl, text[cl].length()); + DisplayServer::get_singleton()->clipboard_set(clipboard); + cut_copy_line = clipboard; } - update(); } -void TextEdit::redo() { - if (readonly) { +void TextEdit::_paste_internal() { + if (!editable) { return; } - _push_current_op(); - if (undo_stack_pos == nullptr) { - return; // Nothing to do. + String clipboard = DisplayServer::get_singleton()->clipboard_get(); + + begin_complex_operation(); + if (has_selection()) { + delete_selection(); + } else if (!cut_copy_line.is_empty() && cut_copy_line == clipboard) { + set_caret_column(0); + String ins = "\n"; + clipboard += ins; } - deselect(); + insert_text_at_caret(clipboard); + end_complex_operation(); +} - TextOperation op = undo_stack_pos->get(); - _do_text_op(op, false); - current_op.version = op.version; - if (undo_stack_pos->get().chain_forward) { - while (true) { - ERR_BREAK(!undo_stack_pos->next()); - undo_stack_pos = undo_stack_pos->next(); - op = undo_stack_pos->get(); - _do_text_op(op, false); - current_op.version = op.version; - if (undo_stack_pos->get().chain_backward) { - break; - } - } +/* Text. */ +// Context menu. +void TextEdit::_generate_context_menu() { + if (!menu) { + menu = memnew(PopupMenu); + add_child(menu, false, INTERNAL_MODE_FRONT); + + menu_dir = memnew(PopupMenu); + menu_dir->set_name("DirMenu"); + menu_dir->add_radio_check_item(RTR("Same as layout direction"), MENU_DIR_INHERITED); + menu_dir->add_radio_check_item(RTR("Auto-detect direction"), MENU_DIR_AUTO); + menu_dir->add_radio_check_item(RTR("Left-to-right"), MENU_DIR_LTR); + menu_dir->add_radio_check_item(RTR("Right-to-left"), MENU_DIR_RTL); + menu->add_child(menu_dir, false, INTERNAL_MODE_FRONT); + + menu_ctl = memnew(PopupMenu); + menu_ctl->set_name("CTLMenu"); + menu_ctl->add_item(RTR("Left-to-right mark (LRM)"), MENU_INSERT_LRM); + menu_ctl->add_item(RTR("Right-to-left mark (RLM)"), MENU_INSERT_RLM); + menu_ctl->add_item(RTR("Start of left-to-right embedding (LRE)"), MENU_INSERT_LRE); + menu_ctl->add_item(RTR("Start of right-to-left embedding (RLE)"), MENU_INSERT_RLE); + menu_ctl->add_item(RTR("Start of left-to-right override (LRO)"), MENU_INSERT_LRO); + menu_ctl->add_item(RTR("Start of right-to-left override (RLO)"), MENU_INSERT_RLO); + menu_ctl->add_item(RTR("Pop direction formatting (PDF)"), MENU_INSERT_PDF); + menu_ctl->add_separator(); + menu_ctl->add_item(RTR("Arabic letter mark (ALM)"), MENU_INSERT_ALM); + menu_ctl->add_item(RTR("Left-to-right isolate (LRI)"), MENU_INSERT_LRI); + menu_ctl->add_item(RTR("Right-to-left isolate (RLI)"), MENU_INSERT_RLI); + menu_ctl->add_item(RTR("First strong isolate (FSI)"), MENU_INSERT_FSI); + menu_ctl->add_item(RTR("Pop direction isolate (PDI)"), MENU_INSERT_PDI); + menu_ctl->add_separator(); + menu_ctl->add_item(RTR("Zero width joiner (ZWJ)"), MENU_INSERT_ZWJ); + menu_ctl->add_item(RTR("Zero width non-joiner (ZWNJ)"), MENU_INSERT_ZWNJ); + menu_ctl->add_item(RTR("Word joiner (WJ)"), MENU_INSERT_WJ); + menu_ctl->add_item(RTR("Soft hyphen (SHY)"), MENU_INSERT_SHY); + menu->add_child(menu_ctl, false, INTERNAL_MODE_FRONT); + + menu->connect("id_pressed", callable_mp(this, &TextEdit::menu_option)); + menu_dir->connect("id_pressed", callable_mp(this, &TextEdit::menu_option)); + menu_ctl->connect("id_pressed", callable_mp(this, &TextEdit::menu_option)); } - _update_scrollbars(); - cursor_set_line(undo_stack_pos->get().to_line, false); - cursor_set_column(undo_stack_pos->get().to_column); - undo_stack_pos = undo_stack_pos->next(); - update(); -} + // Reorganize context menu. + menu->clear(); + if (editable) { + menu->add_item(RTR("Cut"), MENU_CUT, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_cut") : 0); + } + menu->add_item(RTR("Copy"), MENU_COPY, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_copy") : 0); + if (editable) { + menu->add_item(RTR("Paste"), MENU_PASTE, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_paste") : 0); + } + menu->add_separator(); + if (is_selecting_enabled()) { + menu->add_item(RTR("Select All"), MENU_SELECT_ALL, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_text_select_all") : 0); + } + if (editable) { + menu->add_item(RTR("Clear"), MENU_CLEAR); + menu->add_separator(); + menu->add_item(RTR("Undo"), MENU_UNDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_undo") : 0); + menu->add_item(RTR("Redo"), MENU_REDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_redo") : 0); + } + menu->add_separator(); + menu->add_submenu_item(RTR("Text writing direction"), "DirMenu"); + menu->add_separator(); + menu->add_check_item(RTR("Display control characters"), MENU_DISPLAY_UCC); + menu->set_item_checked(menu->get_item_index(MENU_DISPLAY_UCC), draw_control_chars); + if (editable) { + menu->add_submenu_item(RTR("Insert control character"), "CTLMenu"); + } + menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_INHERITED), text_direction == TEXT_DIRECTION_INHERITED); + menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_AUTO), text_direction == TEXT_DIRECTION_AUTO); + menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_LTR), text_direction == TEXT_DIRECTION_LTR); + menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_RTL), text_direction == TEXT_DIRECTION_RTL); -void TextEdit::clear_undo_history() { - saved_version = 0; - current_op.type = TextOperation::TYPE_NONE; - undo_stack_pos = nullptr; - undo_stack.clear(); + if (editable) { + menu->set_item_disabled(menu->get_item_index(MENU_UNDO), !has_undo()); + menu->set_item_disabled(menu->get_item_index(MENU_REDO), !has_redo()); + } } -void TextEdit::begin_complex_operation() { - _push_current_op(); - next_operation_is_complex = true; -} +int TextEdit::_get_menu_action_accelerator(const String &p_action) { + const List<Ref<InputEvent>> *events = InputMap::get_singleton()->action_get_events(p_action); + if (!events) { + return 0; + } -void TextEdit::end_complex_operation() { - _push_current_op(); - ERR_FAIL_COND(undo_stack.size() == 0); + // Use first event in the list for the accelerator. + const List<Ref<InputEvent>>::Element *first_event = events->front(); + if (!first_event) { + return 0; + } - if (undo_stack.back()->get().chain_forward) { - undo_stack.back()->get().chain_forward = false; - return; + const Ref<InputEventKey> event = first_event->get(); + if (event.is_null()) { + return 0; } - undo_stack.back()->get().chain_backward = true; + // Use physical keycode if non-zero + if (event->get_physical_keycode() != 0) { + return event->get_physical_keycode_with_modifiers(); + } else { + return event->get_keycode_with_modifiers(); + } } +/* Versioning */ void TextEdit::_push_current_op() { if (current_op.type == TextOperation::TYPE_NONE) { return; // Nothing to do. @@ -5086,847 +5229,838 @@ void TextEdit::_push_current_op() { } } -void TextEdit::set_tab_size(const int p_size) { - ERR_FAIL_COND_MSG(p_size <= 0, "Tab size must be greater than 0."); - if (p_size == text.get_tab_size()) { - return; +void TextEdit::_do_text_op(const TextOperation &p_op, bool p_reverse) { + ERR_FAIL_COND(p_op.type == TextOperation::TYPE_NONE); + + bool insert = p_op.type == TextOperation::TYPE_INSERT; + if (p_reverse) { + insert = !insert; } - text.set_tab_size(p_size); - text.invalidate_all_lines(); - update(); -} -int TextEdit::get_tab_size() const { - return text.get_tab_size(); + if (insert) { + int check_line; + int check_column; + _base_insert_text(p_op.from_line, p_op.from_column, p_op.text, check_line, check_column); + ERR_FAIL_COND(check_line != p_op.to_line); // BUG. + ERR_FAIL_COND(check_column != p_op.to_column); // BUG. + } else { + _base_remove_text(p_op.from_line, p_op.from_column, p_op.to_line, p_op.to_column); + } } -void TextEdit::set_draw_tabs(bool p_draw) { - draw_tabs = p_draw; - update(); -} +void TextEdit::_clear_redo() { + if (undo_stack_pos == nullptr) { + return; // Nothing to clear. + } -bool TextEdit::is_drawing_tabs() const { - return draw_tabs; -} + _push_current_op(); -void TextEdit::set_draw_spaces(bool p_draw) { - draw_spaces = p_draw; - update(); + while (undo_stack_pos) { + List<TextOperation>::Element *elem = undo_stack_pos; + undo_stack_pos = undo_stack_pos->next(); + undo_stack.erase(elem); + } } -bool TextEdit::is_drawing_spaces() const { - return draw_spaces; -} +/* Search */ +int TextEdit::_get_column_pos_of_word(const String &p_key, const String &p_search, uint32_t p_search_flags, int p_from_column) const { + int col = -1; -void TextEdit::set_override_selected_font_color(bool p_override_selected_font_color) { - override_selected_font_color = p_override_selected_font_color; + if (p_key.length() > 0 && p_search.length() > 0) { + if (p_from_column < 0 || p_from_column > p_search.length()) { + p_from_column = 0; + } + + while (col == -1 && p_from_column <= p_search.length()) { + if (p_search_flags & SEARCH_MATCH_CASE) { + col = p_search.find(p_key, p_from_column); + } else { + col = p_search.findn(p_key, p_from_column); + } + + // Whole words only. + if (col != -1 && p_search_flags & SEARCH_WHOLE_WORDS) { + p_from_column = col; + + if (col > 0 && _is_text_char(p_search[col - 1])) { + col = -1; + } else if ((col + p_key.length()) < p_search.length() && _is_text_char(p_search[col + p_key.length()])) { + col = -1; + } + } + + p_from_column += 1; + } + } + return col; } -bool TextEdit::is_overriding_selected_font_color() const { - return override_selected_font_color; +/* Mouse */ +int TextEdit::_get_char_pos_for_line(int p_px, int p_line, int p_wrap_index) const { + ERR_FAIL_INDEX_V(p_line, text.size(), 0); + p_wrap_index = MIN(p_wrap_index, text.get_line_data(p_line)->get_line_count() - 1); + + RID text_rid = text.get_line_data(p_line)->get_line_rid(p_wrap_index); + if (is_layout_rtl()) { + p_px = TS->shaped_text_get_size(text_rid).x - p_px; + } + return TS->shaped_text_hit_test_position(text_rid, p_px); } -void TextEdit::set_insert_mode(bool p_enabled) { - insert_mode = p_enabled; - update(); +/* Caret */ +void TextEdit::_emit_caret_changed() { + emit_signal(SNAME("caret_changed")); + caret_pos_dirty = false; } -bool TextEdit::is_insert_mode() const { - return insert_mode; +void TextEdit::_reset_caret_blink_timer() { + if (!caret_blink_enabled) { + return; + } + + draw_caret = true; + if (has_focus()) { + caret_blink_timer->stop(); + caret_blink_timer->start(); + update(); + } } -bool TextEdit::is_insert_text_operation() { - return (current_op.type == TextOperation::TYPE_INSERT); +void TextEdit::_toggle_draw_caret() { + draw_caret = !draw_caret; + if (is_visible_in_tree() && has_focus() && window_has_focus) { + update(); + } } -uint32_t TextEdit::get_version() const { - return current_op.version; +int TextEdit::_get_column_x_offset_for_line(int p_char, int p_line) const { + ERR_FAIL_INDEX_V(p_line, text.size(), 0); + + 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)) { + row = i; + break; + } + } + + Rect2 l_caret, t_caret; + TextServer::Direction l_dir, t_dir; + RID text_rid = text.get_line_data(p_line)->get_line_rid(row); + TS->shaped_text_get_carets(text_rid, caret.column, l_caret, l_dir, t_caret, t_dir); + if ((l_caret != Rect2() && (l_dir == TextServer::DIRECTION_AUTO || l_dir == (TextServer::Direction)input_direction)) || (t_caret == Rect2())) { + return l_caret.position.x; + } else { + return t_caret.position.x; + } } -uint32_t TextEdit::get_saved_version() const { - return saved_version; +/* Selection */ +void TextEdit::_click_selection_held() { + // Warning: is_mouse_button_pressed(MOUSE_BUTTON_LEFT) returns false for double+ clicks, so this doesn't work for MODE_WORD + // and MODE_LINE. However, moving the mouse triggers _gui_input, which calls these functions too, so that's not a huge problem. + // I'm unsure if there's an actual fix that doesn't have a ton of side effects. + if (Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT) && selection.selecting_mode != SelectionMode::SELECTION_MODE_NONE) { + switch (selection.selecting_mode) { + case SelectionMode::SELECTION_MODE_POINTER: { + _update_selection_mode_pointer(); + } break; + case SelectionMode::SELECTION_MODE_WORD: { + _update_selection_mode_word(); + } break; + case SelectionMode::SELECTION_MODE_LINE: { + _update_selection_mode_line(); + } break; + default: { + break; + } + } + } else { + click_select_held->stop(); + } } -void TextEdit::tag_saved_version() { - saved_version = get_version(); +void TextEdit::_update_selection_mode_pointer() { + dragging_selection = true; + Point2 mp = get_local_mouse_pos(); + + Point2i pos = get_line_column_at_pos(Point2i(mp.x, mp.y)); + int line = pos.y; + int col = pos.x; + + select(selection.selecting_line, selection.selecting_column, line, col); + + set_caret_line(line, false); + set_caret_column(col); + update(); + + click_select_held->start(); } -double TextEdit::get_scroll_pos_for_line(int p_line, int p_wrap_index) const { - if (!is_wrap_enabled() && !is_hiding_enabled()) { - return p_line; +void TextEdit::_update_selection_mode_word() { + dragging_selection = true; + Point2 mp = get_local_mouse_pos(); + + Point2i pos = get_line_column_at_pos(Point2i(mp.x, mp.y)); + int line = pos.y; + int col = pos.x; + + int caret_pos = CLAMP(col, 0, text[line].length()); + int beg = caret_pos; + int end = beg; + Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid()); + for (int i = 0; i < words.size(); i++) { + if (words[i].x < caret_pos && words[i].y > caret_pos) { + beg = words[i].x; + end = words[i].y; + break; + } } - // Count the number of visible lines up to this line. - double new_line_scroll_pos = 0.0; - int to = CLAMP(p_line, 0, text.size() - 1); - for (int i = 0; i < to; i++) { - if (!text.is_hidden(i)) { - new_line_scroll_pos++; - new_line_scroll_pos += times_line_wraps(i); + /* Initial selection. */ + if (!selection.active) { + select(line, beg, line, end); + selection.selecting_column = beg; + 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); + } else { + if ((col <= selection.selected_word_origin && line == selection.selecting_line) || line < selection.selecting_line) { + selection.selecting_column = selection.selected_word_end; + select(line, beg, selection.selecting_line, selection.selected_word_end); + set_caret_line(selection.from_line, false); + set_caret_column(selection.from_column); + } else { + selection.selecting_column = selection.selected_word_beg; + select(selection.selecting_line, selection.selected_word_beg, line, end); + set_caret_line(selection.to_line, false); + set_caret_column(selection.to_column); } } - new_line_scroll_pos += p_wrap_index; - return new_line_scroll_pos; -} -void TextEdit::set_line_as_first_visible(int p_line, int p_wrap_index) { - set_v_scroll(get_scroll_pos_for_line(p_line, p_wrap_index)); + update(); + + click_select_held->start(); } -void TextEdit::set_line_as_center_visible(int p_line, int p_wrap_index) { - int visible_rows = get_visible_rows(); - int wi; - int first_line = p_line - num_lines_from_rows(p_line, p_wrap_index, -visible_rows / 2, wi) + 1; +void TextEdit::_update_selection_mode_line() { + dragging_selection = true; + Point2 mp = get_local_mouse_pos(); - set_v_scroll(get_scroll_pos_for_line(first_line, wi)); -} + Point2i pos = get_line_column_at_pos(Point2i(mp.x, mp.y)); + int line = pos.y; + int col = pos.x; -void TextEdit::set_line_as_last_visible(int p_line, int p_wrap_index) { - int wi; - int first_line = p_line - num_lines_from_rows(p_line, p_wrap_index, -get_visible_rows() - 1, wi) + 1; + col = 0; + if (line < selection.selecting_line) { + /* Caret is above us. */ + set_caret_line(line - 1, false); + selection.selecting_column = text[selection.selecting_line].length(); + } else { + /* Caret is below us. */ + set_caret_line(line + 1, false); + selection.selecting_column = 0; + col = text[line].length(); + } + set_caret_column(0); - set_v_scroll(get_scroll_pos_for_line(first_line, wi) + get_visible_rows_offset()); -} + select(selection.selecting_line, selection.selecting_column, line, col); + update(); -int TextEdit::get_first_visible_line() const { - return CLAMP(cursor.line_ofs, 0, text.size() - 1); + click_select_held->start(); } -int TextEdit::get_last_full_visible_line() const { - int first_vis_line = get_first_visible_line(); - int last_vis_line = 0; - int wi; - last_vis_line = first_vis_line + num_lines_from_rows(first_vis_line, cursor.wrap_ofs, get_visible_rows(), wi) - 1; - last_vis_line = CLAMP(last_vis_line, 0, text.size() - 1); - return last_vis_line; -} +void TextEdit::_pre_shift_selection() { + if (!selection.active || selection.selecting_mode == SelectionMode::SELECTION_MODE_NONE) { + selection.selecting_line = caret.line; + selection.selecting_column = caret.column; + selection.active = true; + } -int TextEdit::get_last_full_visible_line_wrap_index() const { - int first_vis_line = get_first_visible_line(); - int wi; - num_lines_from_rows(first_vis_line, cursor.wrap_ofs, get_visible_rows(), wi); - return wi; + selection.selecting_mode = SelectionMode::SELECTION_MODE_SHIFT; } -double TextEdit::get_visible_rows_offset() const { - double total = _get_control_height(); - total /= (double)get_row_height(); - total = total - floor(total); - total = -CLAMP(total, 0.001, 1) + 1; - return total; -} +void TextEdit::_post_shift_selection() { + if (selection.active && selection.selecting_mode == SelectionMode::SELECTION_MODE_SHIFT) { + select(selection.selecting_line, selection.selecting_column, caret.line, caret.column); + update(); + } -double TextEdit::get_v_scroll_offset() const { - double val = get_v_scroll() - floor(get_v_scroll()); - return CLAMP(val, 0, 1); + selection.selecting_text = true; } -double TextEdit::get_v_scroll() const { - return v_scroll->get_value(); -} +/* Line Wrapping */ +void TextEdit::_update_wrap_at_column(bool p_force) { + int new_wrap_at = get_size().width - style_normal->get_minimum_size().width - gutters_width - gutter_padding; + if (draw_minimap) { + new_wrap_at -= minimap_width; + } + if (v_scroll->is_visible_in_tree()) { + new_wrap_at -= v_scroll->get_combined_minimum_size().width; + } + /* Give it a little more space. */ + new_wrap_at -= wrap_right_offset; -void TextEdit::set_v_scroll(double p_scroll) { - v_scroll->set_value(p_scroll); - int max_v_scroll = v_scroll->get_max() - v_scroll->get_page(); - if (p_scroll >= max_v_scroll - 1.0) { - _scroll_moved(v_scroll->get_value()); + if ((wrap_at_column != new_wrap_at) || p_force) { + wrap_at_column = new_wrap_at; + if (line_wrapping_mode) { + text.set_width(wrap_at_column); + } else { + text.set_width(-1); + } + text.invalidate_all_lines(); } -} -int TextEdit::get_h_scroll() const { - return h_scroll->get_value(); + _update_caret_wrap_offset(); } -void TextEdit::set_h_scroll(int p_scroll) { - if (p_scroll < 0) { - p_scroll = 0; +void TextEdit::_update_caret_wrap_offset() { + int first_vis_line = get_first_visible_line(); + if (is_line_wrapped(first_vis_line)) { + caret.wrap_ofs = MIN(caret.wrap_ofs, get_line_wrap_count(first_vis_line)); + } else { + caret.wrap_ofs = 0; } - h_scroll->set_value(p_scroll); + set_line_as_first_visible(caret.line_ofs, caret.wrap_ofs); } -void TextEdit::set_smooth_scroll_enabled(bool p_enable) { - v_scroll->set_smooth_scroll_enabled(p_enable); - smooth_scroll_enabled = p_enable; -} +/* Viewport. */ +void TextEdit::_update_scrollbars() { + Size2 size = get_size(); + Size2 hmin = h_scroll->get_combined_minimum_size(); + Size2 vmin = v_scroll->get_combined_minimum_size(); -bool TextEdit::is_smooth_scroll_enabled() const { - return smooth_scroll_enabled; -} + v_scroll->set_begin(Point2(size.width - vmin.width, style_normal->get_margin(SIDE_TOP))); + v_scroll->set_end(Point2(size.width, size.height - style_normal->get_margin(SIDE_TOP) - style_normal->get_margin(SIDE_BOTTOM))); -void TextEdit::set_v_scroll_speed(float p_speed) { - v_scroll_speed = p_speed; -} + h_scroll->set_begin(Point2(0, size.height - hmin.height)); + h_scroll->set_end(Point2(size.width - vmin.width, size.height)); -float TextEdit::get_v_scroll_speed() const { - return v_scroll_speed; -} + int visible_rows = get_visible_line_count(); + int total_rows = get_total_visible_line_count(); + if (scroll_past_end_of_file_enabled) { + total_rows += visible_rows - 1; + } -String TextEdit::get_word_at_pos(const Vector2 &p_pos) const { - int row, col; - _get_mouse_pos(p_pos, row, col); + int visible_width = size.width - style_normal->get_minimum_size().width; + int total_width = text.get_max_width() + vmin.x + gutters_width + gutter_padding; - String s = text[row]; - if (s.length() == 0) { - return ""; + if (draw_minimap) { + total_width += minimap_width; } - int beg, end; - if (select_word(s, col, beg, end)) { - bool inside_quotes = false; - char32_t selected_quote = '\0'; - int qbegin = 0, qend = 0; - for (int i = 0; i < s.length(); i++) { - if (s[i] == '"' || s[i] == '\'') { - if (i == 0 || s[i - 1] != '\\') { - if (inside_quotes && selected_quote == s[i]) { - qend = i; - inside_quotes = false; - selected_quote = '\0'; - if (col >= qbegin && col <= qend) { - return s.substr(qbegin, qend - qbegin); - } - } else if (!inside_quotes) { - qbegin = i + 1; - inside_quotes = true; - selected_quote = s[i]; - } - } - } + + updating_scrolls = true; + + if (total_rows > visible_rows) { + v_scroll->show(); + v_scroll->set_max(total_rows + _get_visible_lines_offset()); + v_scroll->set_page(visible_rows + _get_visible_lines_offset()); + if (smooth_scroll_enabled) { + v_scroll->set_step(0.25); + } else { + v_scroll->set_step(1); } + set_v_scroll(get_v_scroll()); - return s.substr(beg, end - beg); + } else { + caret.line_ofs = 0; + caret.wrap_ofs = 0; + v_scroll->set_value(0); + v_scroll->hide(); } - return String(); -} + if (total_width > visible_width && get_line_wrapping_mode() == LineWrappingMode::LINE_WRAPPING_NONE) { + h_scroll->show(); + h_scroll->set_max(total_width); + h_scroll->set_page(visible_width); + if (caret.x_ofs > (total_width - visible_width)) { + caret.x_ofs = (total_width - visible_width); + } + if (fabs(h_scroll->get_value() - (double)caret.x_ofs) >= 1) { + h_scroll->set_value(caret.x_ofs); + } -String TextEdit::get_tooltip(const Point2 &p_pos) const { - if (!tooltip_obj) { - return Control::get_tooltip(p_pos); + } else { + caret.x_ofs = 0; + h_scroll->set_value(0); + h_scroll->hide(); } - int row, col; - _get_mouse_pos(p_pos, row, col); - String s = text[row]; - if (s.length() == 0) { - return Control::get_tooltip(p_pos); - } - int beg, end; - if (select_word(s, col, beg, end)) { - String tt = tooltip_obj->call(tooltip_func, s.substr(beg, end - beg), tooltip_ud); + updating_scrolls = false; +} - return tt; +int TextEdit::_get_control_height() const { + int control_height = get_size().height; + control_height -= style_normal->get_minimum_size().height; + if (h_scroll->is_visible_in_tree()) { + control_height -= h_scroll->get_size().height; } - - return Control::get_tooltip(p_pos); + return control_height; } -void TextEdit::set_tooltip_request_func(Object *p_obj, const StringName &p_function, const Variant &p_udata) { - tooltip_obj = p_obj; - tooltip_func = p_function; - tooltip_ud = p_udata; +void TextEdit::_v_scroll_input() { + scrolling = false; + minimap_clicked = false; } -void TextEdit::set_line(int line, String new_text) { - if (line < 0 || line >= text.size()) { +void TextEdit::_scroll_moved(double p_to_val) { + if (updating_scrolls) { return; } - _remove_text(line, 0, line, text[line].length()); - _insert_text(line, 0, new_text); - if (cursor.line == line) { - cursor.column = MIN(cursor.column, new_text.length()); - } - if (is_selection_active() && line == selection.to_line && selection.to_column > text[line].length()) { - selection.to_column = text[line].length(); - } -} -void TextEdit::insert_at(const String &p_text, int at) { - _insert_text(at, 0, p_text + "\n"); - if (cursor.line >= at) { - // offset cursor when located after inserted line - ++cursor.line; + if (h_scroll->is_visible_in_tree()) { + caret.x_ofs = h_scroll->get_value(); } - if (is_selection_active()) { - if (selection.from_line >= at) { - // offset selection when located after inserted line - ++selection.from_line; - ++selection.to_line; - } else if (selection.to_line >= at) { - // extend selection that includes inserted line - ++selection.to_line; + if (v_scroll->is_visible_in_tree()) { + // Set line ofs and wrap ofs. + int v_scroll_i = floor(get_v_scroll()); + int sc = 0; + int n_line; + for (n_line = 0; n_line < text.size(); n_line++) { + if (!_is_line_hidden(n_line)) { + sc++; + sc += get_line_wrap_count(n_line); + if (sc > v_scroll_i) { + break; + } + } } - } -} + n_line = MIN(n_line, text.size() - 1); + int line_wrap_amount = get_line_wrap_count(n_line); + int wi = line_wrap_amount - (sc - v_scroll_i - 1); + wi = CLAMP(wi, 0, line_wrap_amount); -void TextEdit::set_show_line_length_guidelines(bool p_show) { - line_length_guidelines = p_show; + caret.line_ofs = n_line; + caret.wrap_ofs = wi; + } update(); } -void TextEdit::set_line_length_guideline_soft_column(int p_column) { - line_length_guideline_soft_col = p_column; - update(); +double TextEdit::_get_visible_lines_offset() const { + double total = _get_control_height(); + total /= (double)get_line_height(); + total = total - floor(total); + total = -CLAMP(total, 0.001, 1) + 1; + return total; } -void TextEdit::set_line_length_guideline_hard_column(int p_column) { - line_length_guideline_hard_col = p_column; - update(); +double TextEdit::_get_v_scroll_offset() const { + double val = get_v_scroll() - floor(get_v_scroll()); + return CLAMP(val, 0, 1); } -void TextEdit::set_draw_minimap(bool p_draw) { - if (draw_minimap != p_draw) { - draw_minimap = p_draw; - _update_wrap_at(); +void TextEdit::_scroll_up(real_t p_delta) { + if (scrolling && smooth_scroll_enabled && SGN(target_v_scroll - v_scroll->get_value()) != SGN(-p_delta)) { + scrolling = false; + minimap_clicked = false; } - update(); -} -bool TextEdit::is_drawing_minimap() const { - return draw_minimap; -} + if (scrolling) { + target_v_scroll = (target_v_scroll - p_delta); + } else { + target_v_scroll = (get_v_scroll() - p_delta); + } -void TextEdit::set_minimap_width(int p_minimap_width) { - if (minimap_width != p_minimap_width) { - minimap_width = p_minimap_width; - _update_wrap_at(); + if (smooth_scroll_enabled) { + if (target_v_scroll <= 0) { + target_v_scroll = 0; + } + if (Math::abs(target_v_scroll - v_scroll->get_value()) < 1.0) { + v_scroll->set_value(target_v_scroll); + } else { + scrolling = true; + set_physics_process_internal(true); + } + } else { + set_v_scroll(target_v_scroll); } - update(); } -int TextEdit::get_minimap_width() const { - return minimap_width; -} +void TextEdit::_scroll_down(real_t p_delta) { + if (scrolling && smooth_scroll_enabled && SGN(target_v_scroll - v_scroll->get_value()) != SGN(p_delta)) { + scrolling = false; + minimap_clicked = false; + } -void TextEdit::set_hiding_enabled(bool p_enabled) { - if (!p_enabled) { - unhide_all_lines(); + if (scrolling) { + target_v_scroll = (target_v_scroll + p_delta); + } else { + target_v_scroll = (get_v_scroll() + p_delta); } - hiding_enabled = p_enabled; - update(); -} -bool TextEdit::is_hiding_enabled() const { - return hiding_enabled; + if (smooth_scroll_enabled) { + int max_v_scroll = round(v_scroll->get_max() - v_scroll->get_page()); + if (target_v_scroll > max_v_scroll) { + target_v_scroll = max_v_scroll; + } + if (Math::abs(target_v_scroll - v_scroll->get_value()) < 1.0) { + v_scroll->set_value(target_v_scroll); + } else { + scrolling = true; + set_physics_process_internal(true); + } + } else { + set_v_scroll(target_v_scroll); + } } -void TextEdit::set_highlight_current_line(bool p_enabled) { - highlight_current_line = p_enabled; - update(); -} +void TextEdit::_scroll_lines_up() { + scrolling = false; + minimap_clicked = false; -bool TextEdit::is_highlight_current_line_enabled() const { - return highlight_current_line; -} + // Adjust the vertical scroll. + set_v_scroll(get_v_scroll() - 1); -bool TextEdit::is_text_field() const { - return true; -} + // Adjust the caret to viewport. + if (!selection.active) { + int cur_line = caret.line; + int cur_wrap = get_caret_wrap_index(); + int last_vis_line = get_last_full_visible_line(); + int last_vis_wrap = get_last_full_visible_line_wrap_index(); -void TextEdit::menu_option(int p_option) { - switch (p_option) { - case MENU_CUT: { - if (!readonly) { - cut(); - } - } break; - case MENU_COPY: { - copy(); - } break; - case MENU_PASTE: { - if (!readonly) { - paste(); - } - } break; - case MENU_CLEAR: { - if (!readonly) { - clear(); - } - } break; - case MENU_SELECT_ALL: { - select_all(); - } break; - case MENU_UNDO: { - undo(); - } break; - case MENU_REDO: { - redo(); - } break; - case MENU_DIR_INHERITED: { - set_text_direction(TEXT_DIRECTION_INHERITED); - } break; - case MENU_DIR_AUTO: { - set_text_direction(TEXT_DIRECTION_AUTO); - } break; - case MENU_DIR_LTR: { - set_text_direction(TEXT_DIRECTION_LTR); - } break; - case MENU_DIR_RTL: { - set_text_direction(TEXT_DIRECTION_RTL); - } break; - case MENU_DISPLAY_UCC: { - set_draw_control_chars(!get_draw_control_chars()); - } break; - case MENU_INSERT_LRM: { - if (!readonly) { - insert_text_at_cursor(String::chr(0x200E)); - } - } break; - case MENU_INSERT_RLM: { - if (!readonly) { - insert_text_at_cursor(String::chr(0x200F)); - } - } break; - case MENU_INSERT_LRE: { - if (!readonly) { - insert_text_at_cursor(String::chr(0x202A)); - } - } break; - case MENU_INSERT_RLE: { - if (!readonly) { - insert_text_at_cursor(String::chr(0x202B)); - } - } break; - case MENU_INSERT_LRO: { - if (!readonly) { - insert_text_at_cursor(String::chr(0x202D)); - } - } break; - case MENU_INSERT_RLO: { - if (!readonly) { - insert_text_at_cursor(String::chr(0x202E)); - } - } break; - case MENU_INSERT_PDF: { - if (!readonly) { - insert_text_at_cursor(String::chr(0x202C)); - } - } break; - case MENU_INSERT_ALM: { - if (!readonly) { - insert_text_at_cursor(String::chr(0x061C)); - } - } break; - case MENU_INSERT_LRI: { - if (!readonly) { - insert_text_at_cursor(String::chr(0x2066)); - } - } break; - case MENU_INSERT_RLI: { - if (!readonly) { - insert_text_at_cursor(String::chr(0x2067)); - } - } break; - case MENU_INSERT_FSI: { - if (!readonly) { - insert_text_at_cursor(String::chr(0x2068)); - } - } break; - case MENU_INSERT_PDI: { - if (!readonly) { - insert_text_at_cursor(String::chr(0x2069)); - } - } break; - case MENU_INSERT_ZWJ: { - if (!readonly) { - insert_text_at_cursor(String::chr(0x200D)); - } - } break; - case MENU_INSERT_ZWNJ: { - if (!readonly) { - insert_text_at_cursor(String::chr(0x200C)); - } - } break; - case MENU_INSERT_WJ: { - if (!readonly) { - insert_text_at_cursor(String::chr(0x2060)); - } - } break; - case MENU_INSERT_SHY: { - if (!readonly) { - insert_text_at_cursor(String::chr(0x00AD)); - } + if (cur_line > last_vis_line || (cur_line == last_vis_line && cur_wrap > last_vis_wrap)) { + set_caret_line(last_vis_line, false, false, last_vis_wrap); } } } -void TextEdit::set_highlighted_word(const String &new_word) { - highlighted_word = new_word; - update(); -} +void TextEdit::_scroll_lines_down() { + scrolling = false; + minimap_clicked = false; -void TextEdit::set_select_identifiers_on_hover(bool p_enable) { - select_identifiers_enabled = p_enable; -} + // Adjust the vertical scroll. + set_v_scroll(get_v_scroll() + 1); -bool TextEdit::is_selecting_identifiers_on_hover_enabled() const { - return select_identifiers_enabled; -} + // Adjust the caret to viewport. + if (!selection.active) { + int cur_line = caret.line; + int cur_wrap = get_caret_wrap_index(); + int first_vis_line = get_first_visible_line(); + int first_vis_wrap = caret.wrap_ofs; -void TextEdit::set_context_menu_enabled(bool p_enable) { - context_menu_enabled = p_enable; + if (cur_line < first_vis_line || (cur_line == first_vis_line && cur_wrap < first_vis_wrap)) { + set_caret_line(first_vis_line, false, false, first_vis_wrap); + } + } } -bool TextEdit::is_context_menu_enabled() { - return context_menu_enabled; -} +// Minimap -void TextEdit::set_shortcut_keys_enabled(bool p_enabled) { - shortcut_keys_enabled = p_enabled; +void TextEdit::_update_minimap_hover() { + const Point2 mp = get_local_mouse_pos(); + const int xmargin_end = get_size().width - style_normal->get_margin(SIDE_RIGHT); - _generate_context_menu(); -} + const bool hovering_sidebar = mp.x > xmargin_end - minimap_width && mp.x < xmargin_end; + if (!hovering_sidebar) { + if (hovering_minimap) { + // Only redraw if the hovering status changed. + hovering_minimap = false; + update(); + } -void TextEdit::set_virtual_keyboard_enabled(bool p_enable) { - virtual_keyboard_enabled = p_enable; -} + // Return early to avoid running the operations below when not needed. + return; + } -void TextEdit::set_selecting_enabled(bool p_enabled) { - selecting_enabled = p_enabled; + const int row = get_minimap_line_at_pos(Point2i(mp.x, mp.y)); - if (!selecting_enabled) { - deselect(); + const bool new_hovering_minimap = row >= get_first_visible_line() && row <= get_last_full_visible_line(); + if (new_hovering_minimap != hovering_minimap) { + // Only redraw if the hovering status changed. + hovering_minimap = new_hovering_minimap; + update(); } - - _generate_context_menu(); } -bool TextEdit::is_selecting_enabled() const { - return selecting_enabled; -} +void TextEdit::_update_minimap_click() { + Point2 mp = get_local_mouse_pos(); -bool TextEdit::is_shortcut_keys_enabled() const { - return shortcut_keys_enabled; -} + int xmargin_end = get_size().width - style_normal->get_margin(SIDE_RIGHT); + if (!dragging_minimap && (mp.x < xmargin_end - minimap_width || mp.y > xmargin_end)) { + minimap_clicked = false; + return; + } + minimap_clicked = true; + dragging_minimap = true; -bool TextEdit::is_virtual_keyboard_enabled() const { - return virtual_keyboard_enabled; -} + int row = get_minimap_line_at_pos(Point2i(mp.x, mp.y)); -PopupMenu *TextEdit::get_menu() const { - return menu; + if (row >= get_first_visible_line() && (row < get_last_full_visible_line() || row >= (text.size() - 1))) { + minimap_scroll_ratio = v_scroll->get_as_ratio(); + minimap_scroll_click_pos = mp.y; + can_drag_minimap = true; + return; + } + + Point2i next_line = get_next_visible_line_index_offset_from(row, 0, -get_visible_line_count() / 2); + int first_line = row - next_line.x + 1; + double delta = get_scroll_pos_for_line(first_line, next_line.y) - get_v_scroll(); + if (delta < 0) { + _scroll_up(-delta); + } else { + _scroll_down(delta); + } } -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); - double value = p_value; - if (value == -1) { - if (opentype_features.has(tag)) { - opentype_features.erase(tag); - text.set_font_features(opentype_features); - text.invalidate_all(); - update(); - } - } else { - if ((double)opentype_features[tag] != value) { - opentype_features[tag] = value; - text.set_font_features(opentype_features); - text.invalidate_all(); - ; - update(); - } - } - notify_property_list_changed(); - return true; +void TextEdit::_update_minimap_drag() { + if (!can_drag_minimap) { + return; } - return false; + int control_height = _get_control_height(); + int scroll_height = v_scroll->get_max() * (minimap_char_size.y + minimap_line_spacing); + if (control_height > scroll_height) { + control_height = scroll_height; + } + + Point2 mp = get_local_mouse_pos(); + + double diff = (mp.y - minimap_scroll_click_pos) / control_height; + v_scroll->set_as_ratio(minimap_scroll_ratio + diff); } -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; +/* Gutters. */ +void TextEdit::_update_gutter_width() { + gutters_width = 0; + for (int i = 0; i < gutters.size(); i++) { + if (gutters[i].draw) { + gutters_width += gutters[i].width; } } - return false; + if (gutters_width > 0) { + gutter_padding = 2; + } + update(); } -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::FLOAT, "opentype_features/" + name)); - } - p_list->push_back(PropertyInfo(Variant::NIL, "opentype_features/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); +/* Syntax highlighting. */ +Dictionary TextEdit::_get_line_syntax_highlighting(int p_line) { + return syntax_highlighter.is_null() && !setting_text ? Dictionary() : syntax_highlighter->get_line_syntax_highlighting(p_line); } -void TextEdit::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &TextEdit::_gui_input); - ClassDB::bind_method(D_METHOD("_cursor_changed_emit"), &TextEdit::_cursor_changed_emit); - ClassDB::bind_method(D_METHOD("_text_changed_emit"), &TextEdit::_text_changed_emit); - ClassDB::bind_method(D_METHOD("_update_wrap_at", "force"), &TextEdit::_update_wrap_at, DEFVAL(false)); +/*** Super internal Core API. Everything builds on it. ***/ - BIND_ENUM_CONSTANT(SEARCH_MATCH_CASE); - BIND_ENUM_CONSTANT(SEARCH_WHOLE_WORDS); - BIND_ENUM_CONSTANT(SEARCH_BACKWARDS); +void TextEdit::_text_changed_emit() { + emit_signal(SNAME("text_changed")); + text_changed_dirty = false; +} - BIND_ENUM_CONSTANT(SELECTION_MODE_NONE); - BIND_ENUM_CONSTANT(SELECTION_MODE_SHIFT); - BIND_ENUM_CONSTANT(SELECTION_MODE_POINTER); - BIND_ENUM_CONSTANT(SELECTION_MODE_WORD); - BIND_ENUM_CONSTANT(SELECTION_MODE_LINE); +void TextEdit::_insert_text(int p_line, int p_char, const String &p_text, int *r_end_line, int *r_end_char) { + if (!setting_text && idle_detect->is_inside_tree()) { + idle_detect->start(); + } - /* - ClassDB::bind_method(D_METHOD("delete_char"),&TextEdit::delete_char); - ClassDB::bind_method(D_METHOD("delete_line"),&TextEdit::delete_line); -*/ + if (undo_enabled) { + _clear_redo(); + } - ClassDB::bind_method(D_METHOD("get_draw_control_chars"), &TextEdit::get_draw_control_chars); - ClassDB::bind_method(D_METHOD("set_draw_control_chars", "enable"), &TextEdit::set_draw_control_chars); - 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); + int retline, retchar; + _base_insert_text(p_line, p_char, p_text, retline, retchar); + if (r_end_line) { + *r_end_line = retline; + } + if (r_end_char) { + *r_end_char = retchar; + } - ClassDB::bind_method(D_METHOD("get_first_non_whitespace_column", "line"), &TextEdit::get_first_non_whitespace_column); - ClassDB::bind_method(D_METHOD("get_indent_level", "line"), &TextEdit::get_indent_level); - ClassDB::bind_method(D_METHOD("set_tab_size", "size"), &TextEdit::set_tab_size); - ClassDB::bind_method(D_METHOD("get_tab_size"), &TextEdit::get_tab_size); + if (!undo_enabled) { + return; + } - ClassDB::bind_method(D_METHOD("set_text", "text"), &TextEdit::set_text); - ClassDB::bind_method(D_METHOD("insert_text_at_cursor", "text"), &TextEdit::insert_text_at_cursor); + /* UNDO!! */ + TextOperation op; + op.type = TextOperation::TYPE_INSERT; + op.from_line = p_line; + op.from_column = p_char; + op.to_line = retline; + op.to_column = retchar; + op.text = p_text; + op.version = ++version; + op.chain_forward = false; + op.chain_backward = false; - ClassDB::bind_method(D_METHOD("get_line_count"), &TextEdit::get_line_count); - ClassDB::bind_method(D_METHOD("get_text"), &TextEdit::get_text); - ClassDB::bind_method(D_METHOD("get_line", "line"), &TextEdit::get_line); - ClassDB::bind_method(D_METHOD("get_visible_line_count"), &TextEdit::get_total_visible_rows); - ClassDB::bind_method(D_METHOD("set_line", "line", "new_text"), &TextEdit::set_line); + // See if it should just be set as current op. + if (current_op.type != op.type) { + op.prev_version = get_version(); + _push_current_op(); + current_op = op; - ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override", "parser"), &TextEdit::set_structured_text_bidi_override); - ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override"), &TextEdit::get_structured_text_bidi_override); - ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override_options", "args"), &TextEdit::set_structured_text_bidi_override_options); - ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override_options"), &TextEdit::get_structured_text_bidi_override_options); + return; // Set as current op, return. + } + // See if it can be merged. + if (current_op.to_line != p_line || current_op.to_column != p_char) { + op.prev_version = get_version(); + _push_current_op(); + current_op = op; + return; // Set as current op, return. + } + // Merge current op. - ClassDB::bind_method(D_METHOD("center_viewport_to_cursor"), &TextEdit::center_viewport_to_cursor); - ClassDB::bind_method(D_METHOD("cursor_set_column", "column", "adjust_viewport"), &TextEdit::cursor_set_column, DEFVAL(true)); - ClassDB::bind_method(D_METHOD("cursor_set_line", "line", "adjust_viewport", "can_be_hidden", "wrap_index"), &TextEdit::cursor_set_line, DEFVAL(true), DEFVAL(true), DEFVAL(0)); + current_op.text += p_text; + current_op.to_column = retchar; + current_op.to_line = retline; + current_op.version = op.version; +} - ClassDB::bind_method(D_METHOD("get_caret_draw_pos"), &TextEdit::get_caret_draw_pos); - ClassDB::bind_method(D_METHOD("is_caret_visible"), &TextEdit::is_caret_visible); - ClassDB::bind_method(D_METHOD("cursor_get_column"), &TextEdit::cursor_get_column); - ClassDB::bind_method(D_METHOD("cursor_get_line"), &TextEdit::cursor_get_line); - ClassDB::bind_method(D_METHOD("cursor_set_blink_enabled", "enable"), &TextEdit::cursor_set_blink_enabled); - ClassDB::bind_method(D_METHOD("cursor_get_blink_enabled"), &TextEdit::cursor_get_blink_enabled); - ClassDB::bind_method(D_METHOD("cursor_set_blink_speed", "blink_speed"), &TextEdit::cursor_set_blink_speed); - ClassDB::bind_method(D_METHOD("cursor_get_blink_speed"), &TextEdit::cursor_get_blink_speed); - ClassDB::bind_method(D_METHOD("cursor_set_block_mode", "enable"), &TextEdit::cursor_set_block_mode); - ClassDB::bind_method(D_METHOD("cursor_is_block_mode"), &TextEdit::cursor_is_block_mode); +void TextEdit::_remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) { + if (!setting_text && idle_detect->is_inside_tree()) { + idle_detect->start(); + } - ClassDB::bind_method(D_METHOD("set_mid_grapheme_caret_enabled", "enabled"), &TextEdit::set_mid_grapheme_caret_enabled); - ClassDB::bind_method(D_METHOD("get_mid_grapheme_caret_enabled"), &TextEdit::get_mid_grapheme_caret_enabled); + String text; + if (undo_enabled) { + _clear_redo(); + text = _base_get_text(p_from_line, p_from_column, p_to_line, p_to_column); + } - ClassDB::bind_method(D_METHOD("set_right_click_moves_caret", "enable"), &TextEdit::set_right_click_moves_caret); - ClassDB::bind_method(D_METHOD("is_right_click_moving_caret"), &TextEdit::is_right_click_moving_caret); + _base_remove_text(p_from_line, p_from_column, p_to_line, p_to_column); - ClassDB::bind_method(D_METHOD("get_selection_mode"), &TextEdit::get_selection_mode); - ClassDB::bind_method(D_METHOD("set_selection_mode", "mode", "line", "column"), &TextEdit::set_selection_mode, DEFVAL(-1), DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("get_selection_line"), &TextEdit::get_selection_line); - ClassDB::bind_method(D_METHOD("get_selection_column"), &TextEdit::get_selection_column); + if (!undo_enabled) { + return; + } - ClassDB::bind_method(D_METHOD("set_readonly", "enable"), &TextEdit::set_readonly); - ClassDB::bind_method(D_METHOD("is_readonly"), &TextEdit::is_readonly); + /* UNDO! */ + TextOperation op; + op.type = TextOperation::TYPE_REMOVE; + op.from_line = p_from_line; + op.from_column = p_from_column; + op.to_line = p_to_line; + op.to_column = p_to_column; + op.text = text; + op.version = ++version; + op.chain_forward = false; + op.chain_backward = false; - ClassDB::bind_method(D_METHOD("set_wrap_enabled", "enable"), &TextEdit::set_wrap_enabled); - ClassDB::bind_method(D_METHOD("is_wrap_enabled"), &TextEdit::is_wrap_enabled); - ClassDB::bind_method(D_METHOD("set_context_menu_enabled", "enable"), &TextEdit::set_context_menu_enabled); - ClassDB::bind_method(D_METHOD("is_context_menu_enabled"), &TextEdit::is_context_menu_enabled); - ClassDB::bind_method(D_METHOD("set_shortcut_keys_enabled", "enable"), &TextEdit::set_shortcut_keys_enabled); - ClassDB::bind_method(D_METHOD("is_shortcut_keys_enabled"), &TextEdit::is_shortcut_keys_enabled); - ClassDB::bind_method(D_METHOD("set_virtual_keyboard_enabled", "enable"), &TextEdit::set_virtual_keyboard_enabled); - ClassDB::bind_method(D_METHOD("is_virtual_keyboard_enabled"), &TextEdit::is_virtual_keyboard_enabled); - ClassDB::bind_method(D_METHOD("set_selecting_enabled", "enable"), &TextEdit::set_selecting_enabled); - ClassDB::bind_method(D_METHOD("is_selecting_enabled"), &TextEdit::is_selecting_enabled); + // See if it should just be set as current op. + if (current_op.type != op.type) { + op.prev_version = get_version(); + _push_current_op(); + current_op = op; + return; // Set as current op, return. + } + // See if it can be merged. + if (current_op.from_line == p_to_line && current_op.from_column == p_to_column) { + // Backspace or similar. + current_op.text = text + current_op.text; + current_op.from_line = p_from_line; + current_op.from_column = p_from_column; + return; // Update current op. + } - ClassDB::bind_method(D_METHOD("delete_selection"), &TextEdit::delete_selection); - ClassDB::bind_method(D_METHOD("backspace"), &TextEdit::backspace); - BIND_VMETHOD(MethodInfo("_backspace")); + op.prev_version = get_version(); + _push_current_op(); + current_op = op; +} - ClassDB::bind_method(D_METHOD("cut"), &TextEdit::cut); - ClassDB::bind_method(D_METHOD("copy"), &TextEdit::copy); - ClassDB::bind_method(D_METHOD("paste"), &TextEdit::paste); +void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, int &r_end_line, int &r_end_column) { + // Save for undo. + ERR_FAIL_INDEX(p_line, text.size()); + ERR_FAIL_COND(p_char < 0); - ClassDB::bind_method(D_METHOD("select", "from_line", "from_column", "to_line", "to_column"), &TextEdit::select); - ClassDB::bind_method(D_METHOD("select_all"), &TextEdit::select_all); - ClassDB::bind_method(D_METHOD("deselect"), &TextEdit::deselect); + /* STEP 1: Remove \r from source text and separate in substrings. */ - ClassDB::bind_method(D_METHOD("is_selection_active"), &TextEdit::is_selection_active); - ClassDB::bind_method(D_METHOD("get_selection_from_line"), &TextEdit::get_selection_from_line); - ClassDB::bind_method(D_METHOD("get_selection_from_column"), &TextEdit::get_selection_from_column); - ClassDB::bind_method(D_METHOD("get_selection_to_line"), &TextEdit::get_selection_to_line); - ClassDB::bind_method(D_METHOD("get_selection_to_column"), &TextEdit::get_selection_to_column); - ClassDB::bind_method(D_METHOD("get_selection_text"), &TextEdit::get_selection_text); - ClassDB::bind_method(D_METHOD("get_word_under_cursor"), &TextEdit::get_word_under_cursor); - ClassDB::bind_method(D_METHOD("search", "key", "flags", "from_line", "from_column"), &TextEdit::_search_bind); + Vector<String> substrings = p_text.replace("\r", "").split("\n"); - ClassDB::bind_method(D_METHOD("undo"), &TextEdit::undo); - ClassDB::bind_method(D_METHOD("redo"), &TextEdit::redo); - ClassDB::bind_method(D_METHOD("clear_undo_history"), &TextEdit::clear_undo_history); + // Is this just a new empty line? + bool shift_first_line = p_char == 0 && p_text.replace("\r", "") == "\n"; - ClassDB::bind_method(D_METHOD("set_draw_tabs"), &TextEdit::set_draw_tabs); - ClassDB::bind_method(D_METHOD("is_drawing_tabs"), &TextEdit::is_drawing_tabs); - ClassDB::bind_method(D_METHOD("set_draw_spaces"), &TextEdit::set_draw_spaces); - ClassDB::bind_method(D_METHOD("is_drawing_spaces"), &TextEdit::is_drawing_spaces); + /* STEP 2: Add spaces if the char is greater than the end of the line. */ + while (p_char > text[p_line].length()) { + text.set(p_line, text[p_line] + String::chr(' '), structured_text_parser(st_parser, st_args, text[p_line] + String::chr(' '))); + } - ClassDB::bind_method(D_METHOD("set_highlight_all_occurrences", "enable"), &TextEdit::set_highlight_all_occurrences); - ClassDB::bind_method(D_METHOD("is_highlight_all_occurrences_enabled"), &TextEdit::is_highlight_all_occurrences_enabled); + /* STEP 3: Separate dest string in pre and post text. */ - 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); + String preinsert_text = text[p_line].substr(0, p_char); + String postinsert_text = text[p_line].substr(p_char, text[p_line].size()); - ClassDB::bind_method(D_METHOD("set_syntax_highlighter", "syntax_highlighter"), &TextEdit::set_syntax_highlighter); - ClassDB::bind_method(D_METHOD("get_syntax_highlighter"), &TextEdit::get_syntax_highlighter); + for (int j = 0; j < substrings.size(); j++) { + // Insert the substrings. - /* Gutters. */ - BIND_ENUM_CONSTANT(GUTTER_TYPE_STRING); - BIND_ENUM_CONSTANT(GUTTER_TPYE_ICON); - BIND_ENUM_CONSTANT(GUTTER_TPYE_CUSTOM); + if (j == 0) { + text.set(p_line, preinsert_text + substrings[j], structured_text_parser(st_parser, st_args, preinsert_text + substrings[j])); + } else { + text.insert(p_line + j, substrings[j], structured_text_parser(st_parser, st_args, substrings[j])); + } - ClassDB::bind_method(D_METHOD("add_gutter", "at"), &TextEdit::add_gutter, DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("remove_gutter", "gutter"), &TextEdit::remove_gutter); - ClassDB::bind_method(D_METHOD("get_gutter_count"), &TextEdit::get_gutter_count); - ClassDB::bind_method(D_METHOD("set_gutter_name", "gutter", "name"), &TextEdit::set_gutter_name); - ClassDB::bind_method(D_METHOD("get_gutter_name", "gutter"), &TextEdit::get_gutter_name); - ClassDB::bind_method(D_METHOD("set_gutter_type", "gutter", "type"), &TextEdit::set_gutter_type); - ClassDB::bind_method(D_METHOD("get_gutter_type", "gutter"), &TextEdit::get_gutter_type); - ClassDB::bind_method(D_METHOD("set_gutter_width", "gutter", "width"), &TextEdit::set_gutter_width); - ClassDB::bind_method(D_METHOD("get_gutter_width", "gutter"), &TextEdit::get_gutter_width); - ClassDB::bind_method(D_METHOD("set_gutter_draw", "gutter", "draw"), &TextEdit::set_gutter_draw); - ClassDB::bind_method(D_METHOD("is_gutter_drawn", "gutter"), &TextEdit::is_gutter_drawn); - ClassDB::bind_method(D_METHOD("set_gutter_clickable", "gutter", "clickable"), &TextEdit::set_gutter_clickable); - ClassDB::bind_method(D_METHOD("is_gutter_clickable", "gutter"), &TextEdit::is_gutter_clickable); - ClassDB::bind_method(D_METHOD("set_gutter_overwritable", "gutter", "overwritable"), &TextEdit::set_gutter_overwritable); - ClassDB::bind_method(D_METHOD("is_gutter_overwritable", "gutter"), &TextEdit::is_gutter_overwritable); - ClassDB::bind_method(D_METHOD("merge_gutters", "from_line", "to_line"), &TextEdit::merge_gutters); - ClassDB::bind_method(D_METHOD("set_gutter_custom_draw", "column", "object", "callback"), &TextEdit::set_gutter_custom_draw); + if (j == substrings.size() - 1) { + text.set(p_line + j, text[p_line + j] + postinsert_text, structured_text_parser(st_parser, st_args, text[p_line + j] + postinsert_text)); + } + } - // Line gutters. - ClassDB::bind_method(D_METHOD("set_line_gutter_metadata", "line", "gutter", "metadata"), &TextEdit::set_line_gutter_metadata); - ClassDB::bind_method(D_METHOD("get_line_gutter_metadata", "line", "gutter"), &TextEdit::get_line_gutter_metadata); - ClassDB::bind_method(D_METHOD("set_line_gutter_text", "line", "gutter", "text"), &TextEdit::set_line_gutter_text); - ClassDB::bind_method(D_METHOD("get_line_gutter_text", "line", "gutter"), &TextEdit::get_line_gutter_text); - ClassDB::bind_method(D_METHOD("set_line_gutter_icon", "line", "gutter", "icon"), &TextEdit::set_line_gutter_icon); - ClassDB::bind_method(D_METHOD("get_line_gutter_icon", "line", "gutter"), &TextEdit::get_line_gutter_icon); - ClassDB::bind_method(D_METHOD("set_line_gutter_item_color", "line", "gutter", "color"), &TextEdit::set_line_gutter_item_color); - ClassDB::bind_method(D_METHOD("get_line_gutter_item_color", "line", "gutter"), &TextEdit::get_line_gutter_item_color); - ClassDB::bind_method(D_METHOD("set_line_gutter_clickable", "line", "gutter", "clickable"), &TextEdit::set_line_gutter_clickable); - ClassDB::bind_method(D_METHOD("is_line_gutter_clickable", "line", "gutter"), &TextEdit::is_line_gutter_clickable); + if (shift_first_line) { + text.move_gutters(p_line, p_line + 1); + text.set_hidden(p_line + 1, text.is_hidden(p_line)); - // Line style - ClassDB::bind_method(D_METHOD("set_line_background_color", "line", "color"), &TextEdit::set_line_background_color); - ClassDB::bind_method(D_METHOD("get_line_background_color", "line"), &TextEdit::get_line_background_color); + text.set_hidden(p_line, false); + } - ClassDB::bind_method(D_METHOD("set_highlight_current_line", "enabled"), &TextEdit::set_highlight_current_line); - ClassDB::bind_method(D_METHOD("is_highlight_current_line_enabled"), &TextEdit::is_highlight_current_line_enabled); + r_end_line = p_line + substrings.size() - 1; + r_end_column = text[r_end_line].length() - postinsert_text.length(); - ClassDB::bind_method(D_METHOD("set_smooth_scroll_enable", "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_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_v_scroll", "value"), &TextEdit::set_v_scroll); - ClassDB::bind_method(D_METHOD("get_v_scroll"), &TextEdit::get_v_scroll); - ClassDB::bind_method(D_METHOD("set_h_scroll", "value"), &TextEdit::set_h_scroll); - ClassDB::bind_method(D_METHOD("get_h_scroll"), &TextEdit::get_h_scroll); + TextServer::Direction dir = TS->shaped_text_get_dominant_direciton_in_range(text.get_line_data(r_end_line)->get_rid(), (r_end_line == p_line) ? caret.column : 0, r_end_column); + if (dir != TextServer::DIRECTION_AUTO) { + input_direction = (TextDirection)dir; + } - ClassDB::bind_method(D_METHOD("menu_option", "option"), &TextEdit::menu_option); - ClassDB::bind_method(D_METHOD("get_menu"), &TextEdit::get_menu); + if (!text_changed_dirty && !setting_text) { + if (is_inside_tree()) { + MessageQueue::get_singleton()->push_call(this, "_text_changed_emit"); + } + text_changed_dirty = true; + } + emit_signal(SNAME("lines_edited_from"), p_line, r_end_line); +} - ClassDB::bind_method(D_METHOD("draw_minimap", "draw"), &TextEdit::set_draw_minimap); - ClassDB::bind_method(D_METHOD("is_drawing_minimap"), &TextEdit::is_drawing_minimap); - ClassDB::bind_method(D_METHOD("set_minimap_width", "width"), &TextEdit::set_minimap_width); - ClassDB::bind_method(D_METHOD("get_minimap_width"), &TextEdit::get_minimap_width); +String TextEdit::_base_get_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) const { + ERR_FAIL_INDEX_V(p_from_line, text.size(), String()); + ERR_FAIL_INDEX_V(p_from_column, text[p_from_line].length() + 1, String()); + ERR_FAIL_INDEX_V(p_to_line, text.size(), String()); + ERR_FAIL_INDEX_V(p_to_column, text[p_to_line].length() + 1, String()); + ERR_FAIL_COND_V(p_to_line < p_from_line, String()); // 'from > to'. + ERR_FAIL_COND_V(p_to_line == p_from_line && p_to_column < p_from_column, String()); // 'from > to'. - ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "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"), "set_language", "get_language"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "readonly"), "set_readonly", "is_readonly"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_current_line"), "set_highlight_current_line", "is_highlight_current_line_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_tabs"), "set_draw_tabs", "is_drawing_tabs"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_spaces"), "set_draw_spaces", "is_drawing_spaces"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_all_occurrences"), "set_highlight_all_occurrences", "is_highlight_all_occurrences_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_selected_font_color"), "set_override_selected_font_color", "is_overriding_selected_font_color"); - 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, "virtual_keyboard_enabled"), "set_virtual_keyboard_enabled", "is_virtual_keyboard_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selecting_enabled"), "set_selecting_enabled", "is_selecting_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_scrolling"), "set_smooth_scroll_enable", "is_smooth_scroll_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "v_scroll_speed"), "set_v_scroll_speed", "get_v_scroll_speed"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "wrap_enabled"), "set_wrap_enabled", "is_wrap_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"); + String ret; - 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"); + for (int i = p_from_line; i <= p_to_line; i++) { + int begin = (i == p_from_line) ? p_from_column : 0; + int end = (i == p_to_line) ? p_to_column : text[i].length(); - ADD_GROUP("Minimap", "minimap_"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "minimap_draw"), "draw_minimap", "is_drawing_minimap"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "minimap_width"), "set_minimap_width", "get_minimap_width"); + if (i > p_from_line) { + ret += "\n"; + } + ret += text[i].substr(begin, end - begin); + } - ADD_GROUP("Caret", "caret_"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_block_mode"), "cursor_set_block_mode", "cursor_is_block_mode"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_blink"), "cursor_set_blink_enabled", "cursor_get_blink_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "caret_blink_speed", PROPERTY_HINT_RANGE, "0.1,10,0.01"), "cursor_set_blink_speed", "cursor_get_blink_speed"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_moving_by_right_click"), "set_right_click_moves_caret", "is_right_click_moving_caret"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_mid_grapheme"), "set_mid_grapheme_caret_enabled", "get_mid_grapheme_caret_enabled"); + return ret; +} - 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"); +void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) { + ERR_FAIL_INDEX(p_from_line, text.size()); + ERR_FAIL_INDEX(p_from_column, text[p_from_line].length() + 1); + ERR_FAIL_INDEX(p_to_line, text.size()); + ERR_FAIL_INDEX(p_to_column, text[p_to_line].length() + 1); + ERR_FAIL_COND(p_to_line < p_from_line); // 'from > to'. + ERR_FAIL_COND(p_to_line == p_from_line && p_to_column < p_from_column); // 'from > to'. - ADD_SIGNAL(MethodInfo("cursor_changed")); - ADD_SIGNAL(MethodInfo("text_changed")); - ADD_SIGNAL(MethodInfo("lines_edited_from", PropertyInfo(Variant::INT, "from_line"), PropertyInfo(Variant::INT, "to_line"))); - ADD_SIGNAL(MethodInfo("gutter_clicked", PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::INT, "gutter"))); - ADD_SIGNAL(MethodInfo("gutter_added")); - ADD_SIGNAL(MethodInfo("gutter_removed")); - ADD_SIGNAL(MethodInfo("symbol_lookup", PropertyInfo(Variant::STRING, "symbol"), PropertyInfo(Variant::INT, "row"), PropertyInfo(Variant::INT, "column"))); - ADD_SIGNAL(MethodInfo("symbol_validate", PropertyInfo(Variant::STRING, "symbol"))); + String pre_text = text[p_from_line].substr(0, p_from_column); + String post_text = text[p_to_line].substr(p_to_column, text[p_to_line].length()); - BIND_ENUM_CONSTANT(MENU_CUT); - BIND_ENUM_CONSTANT(MENU_COPY); - BIND_ENUM_CONSTANT(MENU_PASTE); - BIND_ENUM_CONSTANT(MENU_CLEAR); - BIND_ENUM_CONSTANT(MENU_SELECT_ALL); - BIND_ENUM_CONSTANT(MENU_UNDO); - BIND_ENUM_CONSTANT(MENU_REDO); - BIND_ENUM_CONSTANT(MENU_DIR_INHERITED); - BIND_ENUM_CONSTANT(MENU_DIR_AUTO); - BIND_ENUM_CONSTANT(MENU_DIR_LTR); - BIND_ENUM_CONSTANT(MENU_DIR_RTL); - BIND_ENUM_CONSTANT(MENU_DISPLAY_UCC); - BIND_ENUM_CONSTANT(MENU_INSERT_LRM); - BIND_ENUM_CONSTANT(MENU_INSERT_RLM); - BIND_ENUM_CONSTANT(MENU_INSERT_LRE); - BIND_ENUM_CONSTANT(MENU_INSERT_RLE); - BIND_ENUM_CONSTANT(MENU_INSERT_LRO); - BIND_ENUM_CONSTANT(MENU_INSERT_RLO); - BIND_ENUM_CONSTANT(MENU_INSERT_PDF); - BIND_ENUM_CONSTANT(MENU_INSERT_ALM); - BIND_ENUM_CONSTANT(MENU_INSERT_LRI); - BIND_ENUM_CONSTANT(MENU_INSERT_RLI); - BIND_ENUM_CONSTANT(MENU_INSERT_FSI); - BIND_ENUM_CONSTANT(MENU_INSERT_PDI); - BIND_ENUM_CONSTANT(MENU_INSERT_ZWJ); - BIND_ENUM_CONSTANT(MENU_INSERT_ZWNJ); - BIND_ENUM_CONSTANT(MENU_INSERT_WJ); - BIND_ENUM_CONSTANT(MENU_INSERT_SHY); - BIND_ENUM_CONSTANT(MENU_MAX); + for (int i = p_from_line; i < p_to_line; i++) { + text.remove(p_from_line + 1); + } + text.set(p_from_line, pre_text + post_text, structured_text_parser(st_parser, st_args, pre_text + post_text)); - GLOBAL_DEF("gui/timers/text_edit_idle_detect_sec", 3); - ProjectSettings::get_singleton()->set_custom_property_info("gui/timers/text_edit_idle_detect_sec", PropertyInfo(Variant::FLOAT, "gui/timers/text_edit_idle_detect_sec", PROPERTY_HINT_RANGE, "0,10,0.01,or_greater")); // No negative numbers. - GLOBAL_DEF("gui/common/text_edit_undo_stack_max_size", 1024); - 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. + if (!text_changed_dirty && !setting_text) { + if (is_inside_tree()) { + MessageQueue::get_singleton()->push_call(this, "_text_changed_emit"); + } + text_changed_dirty = true; + } + emit_signal(SNAME("lines_edited_from"), p_to_line, p_from_line); } TextEdit::TextEdit() { @@ -5936,83 +6070,38 @@ TextEdit::TextEdit() { set_default_cursor_shape(CURSOR_IBEAM); text.set_tab_size(text.get_tab_size()); - text.clear(); h_scroll = memnew(HScrollBar); v_scroll = memnew(VScrollBar); - add_child(h_scroll); - add_child(v_scroll); + add_child(h_scroll, false, INTERNAL_MODE_FRONT); + add_child(v_scroll, false, INTERNAL_MODE_FRONT); h_scroll->connect("value_changed", callable_mp(this, &TextEdit::_scroll_moved)); v_scroll->connect("value_changed", callable_mp(this, &TextEdit::_scroll_moved)); v_scroll->connect("scrolling", callable_mp(this, &TextEdit::_v_scroll_input)); + /* Caret. */ caret_blink_timer = memnew(Timer); - add_child(caret_blink_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, &TextEdit::_toggle_draw_caret)); - cursor_set_blink_enabled(false); + set_caret_blink_enabled(false); + + /* Selection. */ + click_select_held = memnew(Timer); + add_child(click_select_held, false, INTERNAL_MODE_FRONT); + click_select_held->set_wait_time(0.05); + click_select_held->connect("timeout", callable_mp(this, &TextEdit::_click_selection_held)); idle_detect = memnew(Timer); - add_child(idle_detect); + add_child(idle_detect, false, INTERNAL_MODE_FRONT); idle_detect->set_one_shot(true); idle_detect->set_wait_time(GLOBAL_GET("gui/timers/text_edit_idle_detect_sec")); idle_detect->connect("timeout", callable_mp(this, &TextEdit::_push_current_op)); - click_select_held = memnew(Timer); - add_child(click_select_held); - click_select_held->set_wait_time(0.05); - click_select_held->connect("timeout", callable_mp(this, &TextEdit::_click_selection_held)); - undo_stack_max_size = GLOBAL_GET("gui/common/text_edit_undo_stack_max_size"); - menu = memnew(PopupMenu); - add_child(menu); - - menu_dir = memnew(PopupMenu); - menu_dir->set_name("DirMenu"); - menu_dir->add_radio_check_item(RTR("Same as layout direction"), MENU_DIR_INHERITED); - menu_dir->add_radio_check_item(RTR("Auto-detect direction"), MENU_DIR_AUTO); - menu_dir->add_radio_check_item(RTR("Left-to-right"), MENU_DIR_LTR); - menu_dir->add_radio_check_item(RTR("Right-to-left"), MENU_DIR_RTL); - menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_INHERITED), true); - menu->add_child(menu_dir); - - menu_ctl = memnew(PopupMenu); - menu_ctl->set_name("CTLMenu"); - menu_ctl->add_item(RTR("Left-to-right mark (LRM)"), MENU_INSERT_LRM); - menu_ctl->add_item(RTR("Right-to-left mark (RLM)"), MENU_INSERT_RLM); - menu_ctl->add_item(RTR("Start of left-to-right embedding (LRE)"), MENU_INSERT_LRE); - menu_ctl->add_item(RTR("Start of right-to-left embedding (RLE)"), MENU_INSERT_RLE); - menu_ctl->add_item(RTR("Start of left-to-right override (LRO)"), MENU_INSERT_LRO); - menu_ctl->add_item(RTR("Start of right-to-left override (RLO)"), MENU_INSERT_RLO); - menu_ctl->add_item(RTR("Pop direction formatting (PDF)"), MENU_INSERT_PDF); - menu_ctl->add_separator(); - menu_ctl->add_item(RTR("Arabic letter mark (ALM)"), MENU_INSERT_ALM); - menu_ctl->add_item(RTR("Left-to-right isolate (LRI)"), MENU_INSERT_LRI); - menu_ctl->add_item(RTR("Right-to-left isolate (RLI)"), MENU_INSERT_RLI); - menu_ctl->add_item(RTR("First strong isolate (FSI)"), MENU_INSERT_FSI); - menu_ctl->add_item(RTR("Pop direction isolate (PDI)"), MENU_INSERT_PDI); - menu_ctl->add_separator(); - menu_ctl->add_item(RTR("Zero width joiner (ZWJ)"), MENU_INSERT_ZWJ); - menu_ctl->add_item(RTR("Zero width non-joiner (ZWNJ)"), MENU_INSERT_ZWNJ); - menu_ctl->add_item(RTR("Word joiner (WJ)"), MENU_INSERT_WJ); - menu_ctl->add_item(RTR("Soft hyphen (SHY)"), MENU_INSERT_SHY); - menu->add_child(menu_ctl); - - set_readonly(false); - menu->connect("id_pressed", callable_mp(this, &TextEdit::menu_option)); - menu_dir->connect("id_pressed", callable_mp(this, &TextEdit::menu_option)); - menu_ctl->connect("id_pressed", callable_mp(this, &TextEdit::menu_option)); -} - -TextEdit::~TextEdit() { -} - -/////////////////////////////////////////////////////////////////////////////// - -Dictionary TextEdit::_get_line_syntax_highlighting(int p_line) { - return syntax_highlighter.is_null() && !setting_text ? Dictionary() : syntax_highlighter->get_line_syntax_highlighting(p_line); + set_editable(true); } diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 146de50275..b1226f2aff 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -42,12 +42,13 @@ class TextEdit : public Control { GDCLASS(TextEdit, Control); public: - enum GutterType { - GUTTER_TYPE_STRING, - GUTTER_TPYE_ICON, - GUTTER_TPYE_CUSTOM + /* Caret. */ + enum CaretType { + CARET_TYPE_LINE, + CARET_TYPE_BLOCK }; + /* Selection */ enum SelectionMode { SELECTION_MODE_NONE, SELECTION_MODE_SHIFT, @@ -56,6 +57,60 @@ public: SELECTION_MODE_LINE }; + /* Line Wrapping.*/ + enum LineWrappingMode { + LINE_WRAPPING_NONE, + LINE_WRAPPING_BOUNDARY + }; + + /* Gutters. */ + enum GutterType { + GUTTER_TYPE_STRING, + GUTTER_TYPE_ICON, + GUTTER_TYPE_CUSTOM + }; + + /* Contex Menu. */ + enum MenuItems { + MENU_CUT, + MENU_COPY, + MENU_PASTE, + MENU_CLEAR, + MENU_SELECT_ALL, + MENU_UNDO, + MENU_REDO, + MENU_DIR_INHERITED, + MENU_DIR_AUTO, + MENU_DIR_LTR, + MENU_DIR_RTL, + MENU_DISPLAY_UCC, + MENU_INSERT_LRM, + MENU_INSERT_RLM, + MENU_INSERT_LRE, + MENU_INSERT_RLE, + MENU_INSERT_LRO, + MENU_INSERT_RLO, + MENU_INSERT_PDF, + MENU_INSERT_ALM, + MENU_INSERT_LRI, + MENU_INSERT_RLI, + MENU_INSERT_FSI, + MENU_INSERT_PDI, + MENU_INSERT_ZWJ, + MENU_INSERT_ZWNJ, + MENU_INSERT_WJ, + MENU_INSERT_SHY, + MENU_MAX + + }; + + /* Search. */ + enum SearchFlags { + SEARCH_MATCH_CASE = 1, + SEARCH_WHOLE_WORDS = 2, + SEARCH_BACKWARDS = 4 + }; + private: struct GutterInfo { GutterType type = GutterType::GUTTER_TYPE_STRING; @@ -68,11 +123,6 @@ private: ObjectID custom_draw_obj = ObjectID(); StringName custom_draw_callback; }; - Vector<GutterInfo> gutters; - int gutters_width = 0; - int gutter_padding = 0; - - void _update_gutter_width(); class Text { public: @@ -94,6 +144,8 @@ private: Color background_color = Color(0, 0, 0, 0); bool hidden = false; + int height = 0; + int width = 0; Line() { data_buf.instantiate(); @@ -101,6 +153,9 @@ private: }; private: + bool is_dirty = false; + bool tab_size_dirty = false; + mutable Vector<Line> text; Ref<Font> font; int font_size = -1; @@ -110,23 +165,28 @@ private: TextServer::Direction direction = TextServer::DIRECTION_AUTO; bool draw_control_chars = false; + int line_height = -1; + int max_width = -1; int width = -1; int tab_size = 4; int gutter_count = 0; + void _calculate_line_height(); + void _calculate_max_line_width(); + public: void set_tab_size(int p_tab_size); 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, String p_language); + void set_direction_and_language(TextServer::Direction p_direction, const String &p_language); void set_draw_control_chars(bool p_draw_control_chars); - int get_line_height(int p_line, int p_wrap_index) const; + int get_line_height() const; int get_line_width(int p_line, int p_wrap_index = -1) const; - int get_max_width(bool p_exclude_hidden = false) const; + int get_max_width() const; void set_width(float p_width); int get_line_wrap_amount(int p_line) const; @@ -135,7 +195,14 @@ private: const Ref<TextParagraph> get_line_data(int p_line) const; void set(int p_line, const String &p_text, const Vector<Vector2i> &p_bidi_override); - void set_hidden(int p_line, bool p_hidden) { text.write[p_line].hidden = p_hidden; } + void set_hidden(int p_line, bool p_hidden) { + text.write[p_line].hidden = p_hidden; + if (!p_hidden && text[p_line].width > max_width) { + max_width = text[p_line].width; + } else if (p_hidden && text[p_line].width == max_width) { + _calculate_max_line_width(); + } + } bool is_hidden(int p_line) const { return text[p_line].hidden; } void insert(int p_at, const String &p_text, const Vector<Vector2i> &p_bidi_override); void remove(int p_at); @@ -159,7 +226,7 @@ private: void set_line_gutter_text(int p_line, int p_gutter, const String &p_text) { text.write[p_line].gutters.write[p_gutter].text = p_text; } const String &get_line_gutter_text(int p_line, int p_gutter) const { return text[p_line].gutters[p_gutter].text; } - void set_line_gutter_icon(int p_line, int p_gutter, Ref<Texture2D> p_icon) { text.write[p_line].gutters.write[p_gutter].icon = p_icon; } + void set_line_gutter_icon(int p_line, int p_gutter, const Ref<Texture2D> &p_icon) { text.write[p_line].gutters.write[p_gutter].icon = p_icon; } const Ref<Texture2D> &get_line_gutter_icon(int p_line, int p_gutter) const { return text[p_line].gutters[p_gutter].icon; } void set_line_gutter_item_color(int p_line, int p_gutter, const Color &p_color) { text.write[p_line].gutters.write[p_gutter].color = p_color; } @@ -173,38 +240,48 @@ private: const Color get_line_background_color(int p_line) const { return text[p_line].background_color; } }; - struct Cursor { - Point2 draw_pos; - bool visible = false; - int last_fit_x = 0; - int line = 0; - int column = 0; ///< cursor - int x_ofs = 0; - int line_ofs = 0; - int wrap_ofs = 0; - } cursor; + /* Text */ + Text text; - struct Selection { - SelectionMode selecting_mode = SelectionMode::SELECTION_MODE_NONE; - int selecting_line = 0; - int selecting_column = 0; - int selected_word_beg = 0; - int selected_word_end = 0; - int selected_word_origin = 0; - bool selecting_text = false; + bool setting_text = false; - bool active = false; + // Text properties. + String ime_text = ""; + Point2 ime_selection; - int from_line = 0; - int from_column = 0; - int to_line = 0; - int to_column = 0; + /* Initialise to opposite first, so we get past the early-out in set_editable. */ + bool editable = false; - bool shiftclick_left = false; - } selection; + TextDirection text_direction = TEXT_DIRECTION_AUTO; + TextDirection input_direction = TEXT_DIRECTION_LTR; - Map<int, Dictionary> syntax_highlighting_cache; + Dictionary opentype_features; + String language = ""; + + Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT; + Array st_args; + + void _clear(); + void _update_caches(); + + // User control. + bool overtype_mode = false; + bool context_menu_enabled = true; + bool shortcut_keys_enabled = true; + bool virtual_keyboard_enabled = true; + + // Overridable actions + String cut_copy_line = ""; + + // Context menu. + PopupMenu *menu = nullptr; + PopupMenu *menu_dir = nullptr; + PopupMenu *menu_ctl = nullptr; + + void _generate_context_menu(); + int _get_menu_action_accelerator(const String &p_action); + /* Versioning */ struct TextOperation { enum Type { TYPE_NONE, @@ -224,370 +301,312 @@ private: bool chain_backward = false; }; - String ime_text; - Point2 ime_selection; + bool undo_enabled = true; + int undo_stack_max_size = 50; - TextOperation current_op; + int complex_operation_count = 0; + bool next_operation_is_complex = false; + TextOperation current_op; List<TextOperation> undo_stack; List<TextOperation>::Element *undo_stack_pos = nullptr; - int undo_stack_max_size; - void _clear_redo(); + Timer *idle_detect; + + uint32_t version = 0; + uint32_t saved_version = 0; + + void _push_current_op(); void _do_text_op(const TextOperation &p_op, bool p_reverse); + void _clear_redo(); - //syntax coloring - Ref<SyntaxHighlighter> syntax_highlighter; - Set<String> keywords; + /* Search */ + Color search_result_color = Color(1, 1, 1); + Color search_result_border_color = Color(1, 1, 1); - Dictionary _get_line_syntax_highlighting(int p_line); + String search_text = ""; + uint32_t search_flags = 0; - bool setting_text = false; + int _get_column_pos_of_word(const String &p_key, const String &p_search, uint32_t p_search_flags, int p_from_column) const; - // data - Text text; + /* Tooltip. */ + Object *tooltip_obj = nullptr; + StringName tooltip_func; + Variant tooltip_ud; - Dictionary opentype_features; - String language; - TextDirection text_direction = TEXT_DIRECTION_AUTO; - TextDirection input_direction = TEXT_DIRECTION_LTR; - Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT; - Array st_args; - bool draw_control_chars = false; + /* Mouse */ + int _get_char_pos_for_line(int p_px, int p_line, int p_wrap_index = 0) const; - uint32_t version = 0; - uint32_t saved_version = 0; + /* Caret. */ + struct Caret { + Point2 draw_pos; + bool visible = false; + int last_fit_x = 0; + int line = 0; + int column = 0; + int x_ofs = 0; + int line_ofs = 0; + int wrap_ofs = 0; + } caret; - int max_chars = 0; - bool readonly = true; // Initialise to opposite first, so we get past the early-out in set_readonly. + bool setting_caret_line = false; + bool caret_pos_dirty = false; - Timer *caret_blink_timer; - bool caret_blink_enabled = false; - bool draw_caret = true; - bool window_has_focus = true; - bool block_caret = false; - bool right_click_moves_caret = true; - bool mid_grapheme_caret_enabled = false; + Color caret_color = Color(1, 1, 1); + Color caret_background_color = Color(0, 0, 0); - bool wrap_enabled = false; - int wrap_at = 0; - int wrap_right_offset = 10; + CaretType caret_type = CaretType::CARET_TYPE_LINE; - bool first_draw = true; - bool setting_row = false; - bool draw_tabs = false; - bool draw_spaces = false; - bool override_selected_font_color = false; - bool cursor_changed_dirty = false; - bool text_changed_dirty = false; - bool undo_enabled = true; - bool line_length_guidelines = false; - int line_length_guideline_soft_col = 80; - int line_length_guideline_hard_col = 100; - bool hiding_enabled = false; - bool draw_minimap = false; - int minimap_width = 80; - Point2 minimap_char_size = Point2(1, 2); - int minimap_line_spacing = 1; + bool draw_caret = true; - bool highlight_all_occurrences = false; - bool scroll_past_end_of_file_enabled = false; - bool brace_matching_enabled = false; - bool highlight_current_line = false; + bool caret_blink_enabled = false; + Timer *caret_blink_timer; - String cut_copy_line; - bool insert_mode = false; - bool select_identifiers_enabled = false; + bool move_caret_on_right_click = true; - bool smooth_scroll_enabled = false; - bool scrolling = false; - bool dragging_selection = false; - bool dragging_minimap = false; - bool can_drag_minimap = false; - bool minimap_clicked = false; - double minimap_scroll_ratio = 0.0; - double minimap_scroll_click_pos = 0.0; - float target_v_scroll = 0.0; - float v_scroll_speed = 80.0; + bool caret_mid_grapheme_enabled = false; - String highlighted_word; + void _emit_caret_changed(); - uint64_t last_dblclk = 0; + void _reset_caret_blink_timer(); + void _toggle_draw_caret(); - Timer *idle_detect; - Timer *click_select_held; - HScrollBar *h_scroll; - VScrollBar *v_scroll; - bool updating_scrolls = false; + int _get_column_x_offset_for_line(int p_char, int p_line) const; - Object *tooltip_obj = nullptr; - StringName tooltip_func; - Variant tooltip_ud; + /* Selection. */ + struct Selection { + SelectionMode selecting_mode = SelectionMode::SELECTION_MODE_NONE; + int selecting_line = 0; + int selecting_column = 0; + int selected_word_beg = 0; + int selected_word_end = 0; + int selected_word_origin = 0; + bool selecting_text = false; - bool next_operation_is_complex = false; + bool active = false; - String search_text; - uint32_t search_flags = 0; - int search_result_line = 0; - int search_result_col = 0; + int from_line = 0; + int from_column = 0; + int to_line = 0; + int to_column = 0; + + bool shiftclick_left = false; + } selection; bool selecting_enabled = true; - bool context_menu_enabled = true; - bool shortcut_keys_enabled = true; - bool virtual_keyboard_enabled = true; + Color font_selected_color = Color(1, 1, 1); + Color selection_color = Color(1, 1, 1); + bool override_selected_font_color = false; - void _generate_context_menu(); + bool dragging_selection = false; - int get_visible_rows() const; - int get_total_visible_rows() const; + Timer *click_select_held; + uint64_t last_dblclk = 0; + Vector2 last_dblclk_pos; + void _click_selection_held(); - int _get_minimap_visible_rows() const; + void _update_selection_mode_pointer(); + void _update_selection_mode_word(); + void _update_selection_mode_line(); - void update_cursor_wrap_offset(); - void _update_wrap_at(bool p_force = false); - Vector<String> get_wrap_rows_text(int p_line) const; - int get_cursor_wrap_index() const; - int get_char_count(); + void _pre_shift_selection(); + void _post_shift_selection(); - double get_scroll_pos_for_line(int p_line, int p_wrap_index = 0) const; - void set_line_as_first_visible(int p_line, int p_wrap_index = 0); - void set_line_as_center_visible(int p_line, int p_wrap_index = 0); - void set_line_as_last_visible(int p_line, int p_wrap_index = 0); - int get_first_visible_line() const; - int get_last_full_visible_line() const; - int get_last_full_visible_line_wrap_index() const; - double get_visible_rows_offset() const; - double get_v_scroll_offset() const; + /* line wrapping. */ + LineWrappingMode line_wrapping_mode = LineWrappingMode::LINE_WRAPPING_NONE; + + int wrap_at_column = 0; + int wrap_right_offset = 10; + + void _update_wrap_at_column(bool p_force = false); + + void _update_caret_wrap_offset(); + + /* Viewport. */ + HScrollBar *h_scroll; + VScrollBar *v_scroll; - int get_char_pos_for_line(int p_px, int p_line, int p_wrap_index = 0) const; - int get_column_x_offset_for_line(int p_char, int p_line) const; + bool scroll_past_end_of_file_enabled = false; + + // Smooth scrolling. + bool smooth_scroll_enabled = false; + float target_v_scroll = 0.0; + float v_scroll_speed = 80.0; + + // Scrolling. + bool scrolling = false; + bool updating_scrolls = false; - void adjust_viewport_to_cursor(); - double get_scroll_line_diff() const; - void _scroll_moved(double); void _update_scrollbars(); + int _get_control_height() const; + void _v_scroll_input(); - void _click_selection_held(); + void _scroll_moved(double p_to_val); - void _update_selection_mode_pointer(); - void _update_selection_mode_word(); - void _update_selection_mode_line(); + double _get_visible_lines_offset() const; + double _get_v_scroll_offset() const; - void _update_minimap_click(); - void _update_minimap_drag(); void _scroll_up(real_t p_delta); void _scroll_down(real_t p_delta); - void _pre_shift_selection(); - void _post_shift_selection(); - void _scroll_lines_up(); void _scroll_lines_down(); - //void mouse_motion(const Point& p_pos, const Point& p_rel, int p_button_mask); - Size2 get_minimum_size() const override; - int _get_control_height() const; + // Minimap + bool draw_minimap = false; - Point2 _get_local_mouse_pos() const; - int _get_menu_action_accelerator(const String &p_action); + int minimap_width = 80; + Point2 minimap_char_size = Point2(1, 2); + int minimap_line_spacing = 1; - void _reset_caret_blink_timer(); - void _toggle_draw_caret(); + // minimap scroll + bool minimap_clicked = false; + bool hovering_minimap = false; + bool dragging_minimap = false; + bool can_drag_minimap = false; - void _update_caches(); - void _cursor_changed_emit(); - void _text_changed_emit(); + double minimap_scroll_ratio = 0.0; + double minimap_scroll_click_pos = 0.0; - void _push_current_op(); + void _update_minimap_hover(); + void _update_minimap_click(); + void _update_minimap_drag(); - /* super internal api, undo/redo builds on it */ + /* Gutters. */ + Vector<GutterInfo> gutters; + int gutters_width = 0; + int gutter_padding = 0; - void _base_insert_text(int p_line, int p_char, const String &p_text, int &r_end_line, int &r_end_column); - String _base_get_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) const; - void _base_remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column); + void _update_gutter_width(); - int _get_column_pos_of_word(const String &p_key, const String &p_search, uint32_t p_search_flags, int p_from_column); + /* Syntax highlighting. */ + Ref<SyntaxHighlighter> syntax_highlighter; + Map<int, Dictionary> syntax_highlighting_cache; - Dictionary _search_bind(const String &p_key, uint32_t p_search_flags, int p_from_line, int p_from_column) const; + Dictionary _get_line_syntax_highlighting(int p_line); - PopupMenu *menu; - PopupMenu *menu_dir; - PopupMenu *menu_ctl; + /* Visual. */ + Ref<StyleBox> style_normal; + Ref<StyleBox> style_focus; + Ref<StyleBox> style_readonly; - void _clear(); + Ref<Texture2D> tab_icon; + Ref<Texture2D> space_icon; + + Ref<Font> font; + int font_size = 16; + Color font_color = Color(1, 1, 1); + Color font_readonly_color = Color(1, 1, 1); + + int outline_size = 0; + Color outline_color = Color(1, 1, 1); + + int line_spacing = 1; + + Color background_color = Color(1, 1, 1); + Color current_line_color = Color(1, 1, 1); + Color word_highlighted_color = Color(1, 1, 1); + + bool window_has_focus = true; + bool first_draw = true; - // Methods used in shortcuts + bool highlight_current_line = false; + bool highlight_all_occurrences = false; + bool draw_control_chars = false; + bool draw_tabs = false; + bool draw_spaces = false; + + /*** Super internal Core API. Everything builds on it. ***/ + bool text_changed_dirty = false; + void _text_changed_emit(); + + void _insert_text(int p_line, int p_char, const String &p_text, int *r_end_line = nullptr, int *r_end_char = nullptr); + void _remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column); + + void _base_insert_text(int p_line, int p_char, const String &p_text, int &r_end_line, int &r_end_column); + String _base_get_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) const; + void _base_remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column); + + /* Input actions. */ void _swap_current_input_direction(); void _new_line(bool p_split_current = true, bool p_above = false); - void _move_cursor_left(bool p_select, bool p_move_by_word = false); - void _move_cursor_right(bool p_select, bool p_move_by_word = false); - void _move_cursor_up(bool p_select); - void _move_cursor_down(bool p_select); - void _move_cursor_to_line_start(bool p_select); - void _move_cursor_to_line_end(bool p_select); - void _move_cursor_page_up(bool p_select); - void _move_cursor_page_down(bool p_select); + void _move_caret_left(bool p_select, bool p_move_by_word = false); + void _move_caret_right(bool p_select, bool p_move_by_word = false); + void _move_caret_up(bool p_select); + void _move_caret_down(bool p_select); + void _move_caret_to_line_start(bool p_select); + void _move_caret_to_line_end(bool p_select); + void _move_caret_page_up(bool p_select); + void _move_caret_page_down(bool p_select); void _do_backspace(bool p_word = false, bool p_all_to_left = false); void _delete(bool p_word = false, bool p_all_to_right = false); - void _move_cursor_document_start(bool p_select); - void _move_cursor_document_end(bool p_select); - void _handle_unicode_character(uint32_t unicode, bool p_had_selection); + void _move_caret_document_start(bool p_select); + void _move_caret_document_end(bool p_select); protected: - bool auto_brace_completion_enabled = false; - - struct Cache { - Ref<Texture2D> tab_icon; - Ref<Texture2D> space_icon; - Ref<Texture2D> folded_eol_icon; - Ref<StyleBox> style_normal; - Ref<StyleBox> style_focus; - Ref<StyleBox> style_readonly; - Ref<Font> font; - int font_size = 16; - int outline_size = 0; - Color outline_color; - Color caret_color; - Color caret_background_color; - Color font_color; - Color font_selected_color; - Color font_readonly_color; - Color selection_color; - Color code_folding_color; - Color current_line_color; - Color line_length_guideline_color; - Color brace_mismatch_color; - Color word_highlighted_color; - Color search_result_color; - Color search_result_border_color; - Color background_color; - - int line_spacing = 1; - int minimap_width = 0; - } cache; - - virtual String get_tooltip(const Point2 &p_pos) const override; - - void _insert_text(int p_line, int p_char, const String &p_text, int *r_end_line = nullptr, int *r_end_char = nullptr); - void _remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column); - void _insert_text_at_cursor(const String &p_text); - virtual void _gui_input(const Ref<InputEvent> &p_gui_input); void _notification(int p_what); - void _consume_pair_symbol(char32_t ch); - void _consume_backspace_for_pair_symbol(int prev_line, int prev_column); - 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: - /* Syntax Highlighting. */ - Ref<SyntaxHighlighter> get_syntax_highlighter(); - void set_syntax_highlighter(Ref<SyntaxHighlighter> p_syntax_highlighter); - - /* Gutters. */ - void add_gutter(int p_at = -1); - void remove_gutter(int p_gutter); - int get_gutter_count() const; - - void set_gutter_name(int p_gutter, const String &p_name); - String get_gutter_name(int p_gutter) const; - - void set_gutter_type(int p_gutter, GutterType p_type); - GutterType get_gutter_type(int p_gutter) const; - - void set_gutter_width(int p_gutter, int p_width); - int get_gutter_width(int p_gutter) const; - int get_total_gutter_width() const; - - void set_gutter_draw(int p_gutter, bool p_draw); - bool is_gutter_drawn(int p_gutter) const; - - void set_gutter_clickable(int p_gutter, bool p_clickable); - bool is_gutter_clickable(int p_gutter) const; - - void set_gutter_overwritable(int p_gutter, bool p_overwritable); - bool is_gutter_overwritable(int p_gutter) const; - - void merge_gutters(int p_from_line, int p_to_line); + /* Internal API for CodeEdit, pending public API. */ + // brace matching + bool highlight_matching_braces_enabled = false; + Color brace_mismatch_color; - void set_gutter_custom_draw(int p_gutter, Object *p_object, const StringName &p_callback); + // Line hiding. + Color code_folding_color = Color(1, 1, 1); + Ref<Texture2D> folded_eol_icon; - // Line gutters. - void set_line_gutter_metadata(int p_line, int p_gutter, const Variant &p_metadata); - Variant get_line_gutter_metadata(int p_line, int p_gutter) const; + bool hiding_enabled = false; - void set_line_gutter_text(int p_line, int p_gutter, const String &p_text); - String get_line_gutter_text(int p_line, int p_gutter) const; + void _set_hiding_enabled(bool p_enabled); + bool _is_hiding_enabled() const; - void set_line_gutter_icon(int p_line, int p_gutter, Ref<Texture2D> p_icon); - Ref<Texture2D> get_line_gutter_icon(int p_line, int p_gutter) const; + void _set_line_as_hidden(int p_line, bool p_hidden); + bool _is_line_hidden(int p_line) const; - void set_line_gutter_item_color(int p_line, int p_gutter, const Color &p_color); - Color get_line_gutter_item_color(int p_line, int p_gutter); + void _unhide_all_lines(); - void set_line_gutter_clickable(int p_line, int p_gutter, bool p_clickable); - bool is_line_gutter_clickable(int p_line, int p_gutter) const; + // Symbol lookup. + String lookup_symbol_word; + void _set_symbol_lookup_word(const String &p_symbol); - // Line style - void set_line_background_color(int p_line, const Color &p_color); - Color get_line_background_color(int p_line); + /* Text manipulation */ - enum MenuItems { - MENU_CUT, - MENU_COPY, - MENU_PASTE, - MENU_CLEAR, - MENU_SELECT_ALL, - MENU_UNDO, - MENU_REDO, - MENU_DIR_INHERITED, - MENU_DIR_AUTO, - MENU_DIR_LTR, - MENU_DIR_RTL, - MENU_DISPLAY_UCC, - MENU_INSERT_LRM, - MENU_INSERT_RLM, - MENU_INSERT_LRE, - MENU_INSERT_RLE, - MENU_INSERT_LRO, - MENU_INSERT_RLO, - MENU_INSERT_PDF, - MENU_INSERT_ALM, - MENU_INSERT_LRI, - MENU_INSERT_RLI, - MENU_INSERT_FSI, - MENU_INSERT_PDI, - MENU_INSERT_ZWJ, - MENU_INSERT_ZWNJ, - MENU_INSERT_WJ, - MENU_INSERT_SHY, - MENU_MAX + // Overridable actions + virtual void _handle_unicode_input_internal(const uint32_t p_unicode); + virtual void _backspace_internal(); - }; + virtual void _cut_internal(); + virtual void _copy_internal(); + virtual void _paste_internal(); - enum SearchFlags { - SEARCH_MATCH_CASE = 1, - SEARCH_WHOLE_WORDS = 2, - SEARCH_BACKWARDS = 4 - }; + GDVIRTUAL1(_handle_unicode_input, int) + GDVIRTUAL0(_backspace) + GDVIRTUAL0(_cut) + GDVIRTUAL0(_copy) + GDVIRTUAL0(_paste) +public: + /* General overrides. */ + virtual void gui_input(const Ref<InputEvent> &p_gui_input) override; + 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; + virtual String get_tooltip(const Point2 &p_pos) const override; + void set_tooltip_request_func(Object *p_obj, const StringName &p_function, const Variant &p_udata); - void _get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) const; - void _get_minimap_mouse_row(const Point2i &p_mouse, int &r_row) const; - - //void delete_char(); - //void delete_line(); - - void begin_complex_operation(); - void end_complex_operation(); + /* Text */ + // Text properties. + bool has_ime_text() const; - bool is_insert_text_operation(); + void set_editable(const bool p_editable); + bool is_editable() const; void set_text_direction(TextDirection p_text_direction); TextDirection get_text_direction() const; @@ -599,203 +618,288 @@ public: 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_options(Array p_args); Array get_structured_text_bidi_override_options() const; - void set_highlighted_word(const String &new_word); - void set_text(String p_text); - void insert_text_at_cursor(const String &p_text); - void insert_at(const String &p_text, int at); + void set_tab_size(const int p_size); + int get_tab_size() const; + + // User controls + void set_overtype_mode_enabled(const bool p_enabled); + bool is_overtype_mode_enabled() const; + + void set_context_menu_enabled(bool p_enable); + bool is_context_menu_enabled() const; + + void set_shortcut_keys_enabled(bool p_enabled); + bool is_shortcut_keys_enabled() const; + + void set_virtual_keyboard_enabled(bool p_enable); + bool is_virtual_keyboard_enabled() const; + + // Text manipulation + void clear(); + + void set_text(const String &p_text); + String get_text() const; int get_line_count() const; - int get_line_width(int p_line, int p_wrap_offset = -1) const; - int get_line_wrap_index_at_col(int p_line, int p_column) const; - - void set_line_as_hidden(int p_line, bool p_hidden); - bool is_line_hidden(int p_line) const; - void unhide_all_lines(); - int num_lines_from(int p_line_from, int visible_amount) const; - int num_lines_from_rows(int p_line_from, int p_wrap_index_from, int visible_amount, int &wrap_index) const; - int get_last_unhidden_line() const; - String get_text(); - String get_line(int line) const; - bool has_ime_text() const; - void set_line(int line, String new_text); - int get_row_height() const; + void set_line(int p_line, const String &p_new_text); + String get_line(int p_line) const; + + int get_line_width(int p_line, int p_wrap_index = -1) const; + int get_line_height() const; int get_indent_level(int p_line) const; int get_first_non_whitespace_column(int p_line) const; - inline void set_scroll_pass_end_of_file(bool p_enabled) { - scroll_past_end_of_file_enabled = p_enabled; - update(); - } - inline void set_auto_brace_completion(bool p_enabled) { - auto_brace_completion_enabled = p_enabled; - } - inline void set_brace_matching(bool p_enabled) { - brace_matching_enabled = p_enabled; - update(); - } + void swap_lines(int p_from_line, int p_to_line); - void center_viewport_to_cursor(); + void insert_line_at(int p_at, const String &p_text); + void insert_text_at_caret(const String &p_text); - void set_mid_grapheme_caret_enabled(const bool p_enabled); - bool get_mid_grapheme_caret_enabled() const; + void remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column); - void cursor_set_column(int p_col, bool p_adjust_viewport = true); - void cursor_set_line(int p_row, bool p_adjust_viewport = true, bool p_can_be_hidden = true, int p_wrap_index = 0); + int get_last_unhidden_line() const; + int get_next_visible_line_offset_from(int p_line_from, int p_visible_amount) const; + Point2i get_next_visible_line_index_offset_from(int p_line_from, int p_wrap_index_from, int p_visible_amount) const; - Point2 get_caret_draw_pos() const; - bool is_caret_visible() const; - int cursor_get_column() const; - int cursor_get_line() const; - Vector2i _get_cursor_pixel_pos(bool p_adjust_viewport = true); + // Overridable actions + void handle_unicode_input(const uint32_t p_unicode); + void backspace(); - bool cursor_get_blink_enabled() const; - void cursor_set_blink_enabled(const bool p_enabled); + void cut(); + void copy(); + void paste(); - float cursor_get_blink_speed() const; - void cursor_set_blink_speed(const float p_speed); + // Context menu. + PopupMenu *get_menu() const; + bool is_menu_visible() const; + void menu_option(int p_option); - void cursor_set_block_mode(const bool p_enable); - bool cursor_is_block_mode() const; + /* Versioning */ + void begin_complex_operation(); + void end_complex_operation(); - void set_right_click_moves_caret(bool p_enable); - bool is_right_click_moving_caret() const; + bool has_undo() const; + bool has_redo() const; + void undo(); + void redo(); + void clear_undo_history(); - SelectionMode get_selection_mode() const; - void set_selection_mode(SelectionMode p_mode, int p_line = -1, int p_column = -1); - int get_selection_line() const; - int get_selection_column() const; + bool is_insert_text_operation() const; - void set_readonly(bool p_readonly); - bool is_readonly() const; + void tag_saved_version(); - void set_max_chars(int p_max_chars); - int get_max_chars() const; + uint32_t get_version() const; + uint32_t get_saved_version() const; - void set_wrap_enabled(bool p_wrap_enabled); - bool is_wrap_enabled() const; - bool line_wraps(int line) const; - int times_line_wraps(int line) const; + /* Search */ + void set_search_text(const String &p_search_text); + void set_search_flags(uint32_t p_flags); - void clear(); + Point2i search(const String &p_key, uint32_t p_search_flags, int p_from_line, int p_from_column) const; - void delete_selection(); + /* Mouse */ + Point2 get_local_mouse_pos() const; + + String get_word_at_pos(const Vector2 &p_pos) const; + + Point2i get_line_column_at_pos(const Point2i &p_pos) const; + int get_minimap_line_at_pos(const Point2i &p_pos) const; + + bool is_dragging_cursor() const; + + /* Caret */ + void set_caret_type(CaretType p_type); + CaretType get_caret_type() const; + + void set_caret_blink_enabled(const bool p_enabled); + bool is_caret_blink_enabled() const; + + void set_caret_blink_speed(const float p_speed); + float get_caret_blink_speed() const; + + void set_move_caret_on_right_click_enabled(const bool p_enable); + bool is_move_caret_on_right_click_enabled() const; + + void set_caret_mid_grapheme_enabled(const bool p_enabled); + bool is_caret_mid_grapheme_enabled() const; + + bool is_caret_visible() const; + Point2 get_caret_draw_pos() const; + + void set_caret_line(int p_line, bool p_adjust_viewport = true, bool p_can_be_hidden = true, int p_wrap_index = 0); + int get_caret_line() const; + + void set_caret_column(int p_col, bool p_adjust_viewport = true); + int get_caret_column() const; + + int get_caret_wrap_index() const; + + String get_word_under_caret() const; + + /* Selection. */ + void set_selecting_enabled(const bool p_enabled); + bool is_selecting_enabled() const; + + void set_override_selected_font_color(bool p_override_selected_font_color); + bool is_overriding_selected_font_color() const; + + void set_selection_mode(SelectionMode p_mode, int p_line = -1, int p_column = -1); + SelectionMode get_selection_mode() const; - virtual void backspace(); - void cut(); - void copy(); - void paste(); void select_all(); void select_word_under_caret(); void select(int p_from_line, int p_from_column, int p_to_line, int p_to_column); - void deselect(); - void swap_lines(int line1, int line2); - void set_search_text(const String &p_search_text); - void set_search_flags(uint32_t p_flags); - void set_current_search_result(int line, int col); + bool has_selection() const; + + String get_selected_text() const; + + int get_selection_line() const; + int get_selection_column() const; - void set_highlight_all_occurrences(const bool p_enabled); - bool is_highlight_all_occurrences_enabled() const; - bool is_selection_active() const; int get_selection_from_line() const; int get_selection_from_column() const; int get_selection_to_line() const; int get_selection_to_column() const; - String get_selection_text() const; - String get_word_under_cursor() const; - String get_word_at_pos(const Vector2 &p_pos) const; + void deselect(); + void delete_selection(); - bool search(const String &p_key, uint32_t p_search_flags, int p_from_line, int p_from_column, int &r_line, int &r_column) const; + /* line wrapping. */ + void set_line_wrapping_mode(LineWrappingMode p_wrapping_mode); + LineWrappingMode get_line_wrapping_mode() const; - void undo(); - void redo(); - void clear_undo_history(); + bool is_line_wrapped(int p_line) const; + int get_line_wrap_count(int p_line) const; + int get_line_wrap_index_at_column(int p_line, int p_column) const; - void set_tab_size(const int p_size); - int get_tab_size() const; - void set_draw_tabs(bool p_draw); - bool is_drawing_tabs() const; - void set_draw_spaces(bool p_draw); - bool is_drawing_spaces() const; - void set_override_selected_font_color(bool p_override_selected_font_color); - bool is_overriding_selected_font_color() const; + Vector<String> get_line_wrapped_text(int p_line) const; - void set_insert_mode(bool p_enabled); - bool is_insert_mode() const; + /* Viewport. */ + // Scrolling. + void set_smooth_scroll_enabled(const bool p_enable); + bool is_smooth_scroll_enabled() const; + + void set_scroll_past_end_of_file_enabled(const bool p_enabled); + bool is_scroll_past_end_of_file_enabled() const; - double get_v_scroll() const; void set_v_scroll(double p_scroll); + double get_v_scroll() const; - int get_h_scroll() const; void set_h_scroll(int p_scroll); - - void set_smooth_scroll_enabled(bool p_enable); - bool is_smooth_scroll_enabled() const; + int get_h_scroll() const; void set_v_scroll_speed(float p_speed); float get_v_scroll_speed() const; - uint32_t get_version() const; - uint32_t get_saved_version() const; - void tag_saved_version(); + double get_scroll_pos_for_line(int p_line, int p_wrap_index = 0) const; - void menu_option(int p_option); + // Visible lines. + void set_line_as_first_visible(int p_line, int p_wrap_index = 0); + int get_first_visible_line() const; - void set_highlight_current_line(bool p_enabled); - bool is_highlight_current_line_enabled() const; + void set_line_as_center_visible(int p_line, int p_wrap_index = 0); - void set_show_line_length_guidelines(bool p_show); - void set_line_length_guideline_soft_column(int p_column); - void set_line_length_guideline_hard_column(int p_column); + void set_line_as_last_visible(int p_line, int p_wrap_index = 0); + int get_last_full_visible_line() const; + int get_last_full_visible_line_wrap_index() const; + + int get_visible_line_count() const; + int get_total_visible_line_count() const; + + // Auto Adjust + void adjust_viewport_to_caret(); + void center_viewport_to_caret(); + // Minimap void set_draw_minimap(bool p_draw); bool is_drawing_minimap() const; void set_minimap_width(int p_minimap_width); int get_minimap_width() const; - void set_hiding_enabled(bool p_enabled); - bool is_hiding_enabled() const; + int get_minimap_visible_lines() const; - void set_tooltip_request_func(Object *p_obj, const StringName &p_function, const Variant &p_udata); + /* Gutters. */ + void add_gutter(int p_at = -1); + void remove_gutter(int p_gutter); + int get_gutter_count() const; - void set_select_identifiers_on_hover(bool p_enable); - bool is_selecting_identifiers_on_hover_enabled() const; + void set_gutter_name(int p_gutter, const String &p_name); + String get_gutter_name(int p_gutter) const; - void set_context_menu_enabled(bool p_enable); - bool is_context_menu_enabled(); + void set_gutter_type(int p_gutter, GutterType p_type); + GutterType get_gutter_type(int p_gutter) const; - void set_selecting_enabled(bool p_enabled); - bool is_selecting_enabled() const; + void set_gutter_width(int p_gutter, int p_width); + int get_gutter_width(int p_gutter) const; + int get_total_gutter_width() const; - void set_shortcut_keys_enabled(bool p_enabled); - bool is_shortcut_keys_enabled() const; + void set_gutter_draw(int p_gutter, bool p_draw); + bool is_gutter_drawn(int p_gutter) const; - void set_virtual_keyboard_enabled(bool p_enable); - bool is_virtual_keyboard_enabled() const; + void set_gutter_clickable(int p_gutter, bool p_clickable); + bool is_gutter_clickable(int p_gutter) const; - PopupMenu *get_menu() const; + void set_gutter_overwritable(int p_gutter, bool p_overwritable); + bool is_gutter_overwritable(int p_gutter) const; - String get_text_for_lookup_completion(); + void merge_gutters(int p_from_line, int p_to_line); + + void set_gutter_custom_draw(int p_gutter, Object *p_object, const StringName &p_callback); + + // Line gutters. + void set_line_gutter_metadata(int p_line, int p_gutter, const Variant &p_metadata); + Variant get_line_gutter_metadata(int p_line, int p_gutter) const; + + void set_line_gutter_text(int p_line, int p_gutter, const String &p_text); + String get_line_gutter_text(int p_line, int p_gutter) const; + + void set_line_gutter_icon(int p_line, int p_gutter, const Ref<Texture2D> &p_icon); + Ref<Texture2D> get_line_gutter_icon(int p_line, int p_gutter) const; + + void set_line_gutter_item_color(int p_line, int p_gutter, const Color &p_color); + Color get_line_gutter_item_color(int p_line, int p_gutter) const; + + void set_line_gutter_clickable(int p_line, int p_gutter, bool p_clickable); + bool is_line_gutter_clickable(int p_line, int p_gutter) const; + + // Line style + void set_line_background_color(int p_line, const Color &p_color); + Color get_line_background_color(int p_line) const; + + /* Syntax Highlighting. */ + void set_syntax_highlighter(Ref<SyntaxHighlighter> p_syntax_highlighter); + Ref<SyntaxHighlighter> get_syntax_highlighter() const; + + /* Visual. */ + void set_highlight_current_line(bool p_enabled); + bool is_highlight_current_line_enabled() const; + + void set_highlight_all_occurrences(const bool p_enabled); + bool is_highlight_all_occurrences_enabled() const; + + void set_draw_control_chars(bool p_draw_control_chars); + bool get_draw_control_chars() const; + + void set_draw_tabs(bool p_draw); + bool is_drawing_tabs() const; + + void set_draw_spaces(bool p_draw); + bool is_drawing_spaces() const; - virtual bool is_text_field() const override; TextEdit(); - ~TextEdit(); }; -VARIANT_ENUM_CAST(TextEdit::GutterType); +VARIANT_ENUM_CAST(TextEdit::CaretType); +VARIANT_ENUM_CAST(TextEdit::LineWrappingMode); VARIANT_ENUM_CAST(TextEdit::SelectionMode); +VARIANT_ENUM_CAST(TextEdit::GutterType); VARIANT_ENUM_CAST(TextEdit::MenuItems); VARIANT_ENUM_CAST(TextEdit::SearchFlags); diff --git a/scene/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp index 5e5dec3579..286f01ee33 100644 --- a/scene/gui/texture_progress_bar.cpp +++ b/scene/gui/texture_progress_bar.cpp @@ -100,6 +100,15 @@ Ref<Texture2D> TextureProgressBar::get_progress_texture() const { return progress; } +void TextureProgressBar::set_progress_offset(Point2 p_offset) { + progress_offset = p_offset; + update(); +} + +Point2 TextureProgressBar::get_progress_offset() const { + return progress_offset; +} + void TextureProgressBar::set_tint_under(const Color &p_tint) { tint_under = p_tint; update(); @@ -360,6 +369,9 @@ void TextureProgressBar::draw_nine_patch_stretched(const Ref<Texture2D> &p_textu } } + if (p_texture == progress) { + dst_rect.position += progress_offset; + } p_texture->get_rect_region(dst_rect, src_rect, dst_rect, src_rect); RID ci = get_canvas_item(); @@ -403,20 +415,24 @@ void TextureProgressBar::_notification(int p_what) { Size2 s = progress->get_size(); switch (mode) { case FILL_LEFT_TO_RIGHT: { - Rect2 region = Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)); - draw_texture_rect_region(progress, region, region, tint_progress); + Rect2 region = Rect2(progress_offset, Size2(s.x * get_as_ratio(), s.y)); + Rect2 source = Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)); + draw_texture_rect_region(progress, region, source, tint_progress); } break; case FILL_RIGHT_TO_LEFT: { - Rect2 region = Rect2(Point2(s.x - s.x * get_as_ratio(), 0), Size2(s.x * get_as_ratio(), s.y)); - draw_texture_rect_region(progress, region, region, tint_progress); + Rect2 region = Rect2(progress_offset + Point2(s.x - s.x * get_as_ratio(), 0), Size2(s.x * get_as_ratio(), s.y)); + Rect2 source = Rect2(Point2(s.x - s.x * get_as_ratio(), 0), Size2(s.x * get_as_ratio(), s.y)); + draw_texture_rect_region(progress, region, source, tint_progress); } break; case FILL_TOP_TO_BOTTOM: { - Rect2 region = Rect2(Point2(), Size2(s.x, s.y * get_as_ratio())); - draw_texture_rect_region(progress, region, region, tint_progress); + Rect2 region = Rect2(progress_offset + Point2(), Size2(s.x, s.y * get_as_ratio())); + Rect2 source = Rect2(Point2(), Size2(s.x, s.y * get_as_ratio())); + draw_texture_rect_region(progress, region, source, tint_progress); } break; case FILL_BOTTOM_TO_TOP: { - Rect2 region = Rect2(Point2(0, s.y - s.y * get_as_ratio()), Size2(s.x, s.y * get_as_ratio())); - draw_texture_rect_region(progress, region, region, tint_progress); + Rect2 region = Rect2(progress_offset + Point2(0, s.y - s.y * get_as_ratio()), Size2(s.x, s.y * get_as_ratio())); + Rect2 source = Rect2(Point2(0, s.y - s.y * get_as_ratio()), Size2(s.x, s.y * get_as_ratio())); + draw_texture_rect_region(progress, region, source, tint_progress); } break; case FILL_CLOCKWISE: case FILL_COUNTER_CLOCKWISE: @@ -427,8 +443,9 @@ void TextureProgressBar::_notification(int p_what) { float val = get_as_ratio() * rad_max_degrees / 360; if (val == 1) { - Rect2 region = Rect2(Point2(), s); - draw_texture_rect(progress, region, false, tint_progress); + Rect2 region = Rect2(progress_offset, s); + Rect2 source = Rect2(Point2(), s); + draw_texture_rect_region(progress, region, source, tint_progress); } else if (val != 0) { Array pts; float direction = mode == FILL_COUNTER_CLOCKWISE ? -1 : 1; @@ -454,14 +471,14 @@ void TextureProgressBar::_notification(int p_what) { Vector<Point2> uvs; Vector<Point2> points; uvs.push_back(get_relative_center()); - points.push_back(Point2(s.x * get_relative_center().x, s.y * get_relative_center().y)); + points.push_back(progress_offset + Point2(s.x * get_relative_center().x, s.y * get_relative_center().y)); for (int i = 0; i < pts.size(); i++) { Point2 uv = unit_val_to_uv(pts[i]); if (uvs.find(uv) >= 0) { continue; } uvs.push_back(uv); - points.push_back(Point2(uv.x * s.x, uv.y * s.y)); + points.push_back(progress_offset + Point2(uv.x * s.x, uv.y * s.y)); } Vector<Color> colors; colors.push_back(tint_progress); @@ -484,17 +501,19 @@ void TextureProgressBar::_notification(int p_what) { } } break; case FILL_BILINEAR_LEFT_AND_RIGHT: { - Rect2 region = Rect2(Point2(s.x / 2 - s.x * get_as_ratio() / 2, 0), Size2(s.x * get_as_ratio(), s.y)); - draw_texture_rect_region(progress, region, region, tint_progress); + Rect2 region = Rect2(progress_offset + Point2(s.x / 2 - s.x * get_as_ratio() / 2, 0), Size2(s.x * get_as_ratio(), s.y)); + Rect2 source = Rect2(Point2(s.x / 2 - s.x * get_as_ratio() / 2, 0), Size2(s.x * get_as_ratio(), s.y)); + draw_texture_rect_region(progress, region, source, tint_progress); } break; case FILL_BILINEAR_TOP_AND_BOTTOM: { - Rect2 region = Rect2(Point2(0, s.y / 2 - s.y * get_as_ratio() / 2), Size2(s.x, s.y * get_as_ratio())); - draw_texture_rect_region(progress, region, region, tint_progress); + Rect2 region = Rect2(progress_offset + Point2(0, s.y / 2 - s.y * get_as_ratio() / 2), Size2(s.x, s.y * get_as_ratio())); + Rect2 source = Rect2(Point2(0, s.y / 2 - s.y * get_as_ratio() / 2), Size2(s.x, s.y * get_as_ratio())); + draw_texture_rect_region(progress, region, source, tint_progress); } break; case FILL_MODE_MAX: break; default: - draw_texture_rect_region(progress, Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)), Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)), tint_progress); + draw_texture_rect_region(progress, Rect2(progress_offset, Size2(s.x * get_as_ratio(), s.y)), Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)), tint_progress); } } if (over.is_valid()) { @@ -585,6 +604,9 @@ void TextureProgressBar::_bind_methods() { ClassDB::bind_method(D_METHOD("set_tint_over", "tint"), &TextureProgressBar::set_tint_over); ClassDB::bind_method(D_METHOD("get_tint_over"), &TextureProgressBar::get_tint_over); + ClassDB::bind_method(D_METHOD("set_texture_progress_offset", "offset"), &TextureProgressBar::set_progress_offset); + ClassDB::bind_method(D_METHOD("get_texture_progress_offset"), &TextureProgressBar::get_progress_offset); + ClassDB::bind_method(D_METHOD("set_radial_initial_angle", "mode"), &TextureProgressBar::set_radial_initial_angle); ClassDB::bind_method(D_METHOD("get_radial_initial_angle"), &TextureProgressBar::get_radial_initial_angle); @@ -604,6 +626,7 @@ void TextureProgressBar::_bind_methods() { 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::INT, "fill_mode", PROPERTY_HINT_ENUM, "Left to Right,Right to Left,Top to Bottom,Bottom to Top,Clockwise,Counter Clockwise,Bilinear (Left and Right),Bilinear (Top and Bottom),Clockwise and Counter Clockwise"), "set_fill_mode", "get_fill_mode"); ADD_GROUP("Tint", "tint_"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_under"), "set_tint_under", "get_tint_under"); diff --git a/scene/gui/texture_progress_bar.h b/scene/gui/texture_progress_bar.h index d147c43a26..c508f41387 100644 --- a/scene/gui/texture_progress_bar.h +++ b/scene/gui/texture_progress_bar.h @@ -61,6 +61,9 @@ public: void set_fill_mode(int p_fill); int get_fill_mode(); + void set_progress_offset(Point2 p_offset); + Point2 get_progress_offset() const; + void set_radial_initial_angle(float p_angle); float get_radial_initial_angle(); @@ -100,6 +103,7 @@ public: private: FillMode mode = FILL_LEFT_TO_RIGHT; + Point2 progress_offset; float rad_init_angle = 0.0; float rad_max_degrees = 360.0; Point2 rad_center_off; diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index fd9e87604f..f62c09925d 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -99,39 +99,44 @@ void TreeItem::_change_tree(Tree *p_tree) { c = c->next; } - if (tree && tree->root == this) { - tree->root = nullptr; - } + if (tree) { + if (tree->root == this) { + tree->root = nullptr; + } - if (tree && tree->popup_edited_item == this) { - tree->popup_edited_item = nullptr; - tree->pressing_for_editor = false; - } + if (tree->popup_edited_item == this) { + tree->popup_edited_item = nullptr; + tree->pressing_for_editor = false; + } - if (tree && tree->cache.hover_item == this) { - tree->cache.hover_item = nullptr; - } + if (tree->cache.hover_item == this) { + tree->cache.hover_item = nullptr; + } - if (tree && tree->selected_item == this) { - tree->selected_item = nullptr; - } + if (tree->selected_item == this) { + tree->selected_item = nullptr; + } - if (tree && tree->drop_mode_over == this) { - tree->drop_mode_over = nullptr; - } + if (tree->drop_mode_over == this) { + tree->drop_mode_over = nullptr; + } - if (tree && tree->single_select_defer == this) { - tree->single_select_defer = nullptr; - } + if (tree->single_select_defer == this) { + tree->single_select_defer = nullptr; + } + + if (tree->edited_item == this) { + tree->edited_item = nullptr; + tree->pressing_for_editor = false; + } - if (tree && tree->edited_item == this) { - tree->edited_item = nullptr; - tree->pressing_for_editor = false; + tree->update(); } tree = p_tree; if (tree) { + tree->update(); cells.resize(tree->columns.size()); } } @@ -151,6 +156,7 @@ void TreeItem::set_cell_mode(int p_column, TreeCellMode p_mode) { c.dirty = true; c.icon_max_w = 0; _changed_notify(p_column); + cached_minimum_size_dirty = true; } TreeItem::TreeCellMode TreeItem::get_cell_mode(int p_column) const { @@ -162,7 +168,21 @@ TreeItem::TreeCellMode TreeItem::get_cell_mode(int p_column) const { void TreeItem::set_checked(int p_column, bool p_checked) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].checked = p_checked; + cells.write[p_column].indeterminate = false; _changed_notify(p_column); + cached_minimum_size_dirty = true; +} + +void TreeItem::set_indeterminate(int p_column, bool p_indeterminate) { + ERR_FAIL_INDEX(p_column, cells.size()); + // Prevent uncheck if indeterminate set to false twice + if (p_indeterminate == cells[p_column].indeterminate) { + return; + } + cells.write[p_column].indeterminate = p_indeterminate; + cells.write[p_column].checked = false; + _changed_notify(p_column); + cached_minimum_size_dirty = true; } bool TreeItem::is_checked(int p_column) const { @@ -170,6 +190,11 @@ bool TreeItem::is_checked(int p_column) const { return cells[p_column].checked; } +bool TreeItem::is_indeterminate(int p_column) const { + ERR_FAIL_INDEX_V(p_column, cells.size(), false); + return cells[p_column].indeterminate; +} + void TreeItem::set_text(int p_column, String p_text) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].text = p_text; @@ -190,6 +215,7 @@ void TreeItem::set_text(int p_column, String p_text) { cells.write[p_column].step = 0; } _changed_notify(p_column); + cached_minimum_size_dirty = true; } String TreeItem::get_text(int p_column) const { @@ -205,6 +231,7 @@ void TreeItem::set_text_direction(int p_column, Control::TextDirection p_text_di cells.write[p_column].dirty = true; _changed_notify(p_column); } + cached_minimum_size_dirty = true; } Control::TextDirection TreeItem::get_text_direction(int p_column) const { @@ -217,6 +244,7 @@ void TreeItem::clear_opentype_features(int p_column) { cells.write[p_column].opentype_features.clear(); cells.write[p_column].dirty = true; _changed_notify(p_column); + cached_minimum_size_dirty = true; } void TreeItem::set_opentype_feature(int p_column, const String &p_name, int p_value) { @@ -226,6 +254,7 @@ void TreeItem::set_opentype_feature(int p_column, const String &p_name, int p_va cells.write[p_column].opentype_features[tag] = p_value; cells.write[p_column].dirty = true; _changed_notify(p_column); + cached_minimum_size_dirty = true; } } @@ -244,6 +273,7 @@ void TreeItem::set_structured_text_bidi_override(int p_column, Control::Structur cells.write[p_column].st_parser = p_parser; cells.write[p_column].dirty = true; _changed_notify(p_column); + cached_minimum_size_dirty = true; } } @@ -257,6 +287,7 @@ void TreeItem::set_structured_text_bidi_override_options(int p_column, Array p_a cells.write[p_column].st_args = p_args; cells.write[p_column].dirty = true; _changed_notify(p_column); + cached_minimum_size_dirty = true; } Array TreeItem::get_structured_text_bidi_override_options(int p_column) const { @@ -270,6 +301,7 @@ void TreeItem::set_language(int p_column, const String &p_language) { cells.write[p_column].language = p_language; cells.write[p_column].dirty = true; _changed_notify(p_column); + cached_minimum_size_dirty = true; } } @@ -283,6 +315,7 @@ void TreeItem::set_suffix(int p_column, String p_suffix) { cells.write[p_column].suffix = p_suffix; _changed_notify(p_column); + cached_minimum_size_dirty = true; } String TreeItem::get_suffix(int p_column) const { @@ -294,6 +327,7 @@ void TreeItem::set_icon(int p_column, const Ref<Texture2D> &p_icon) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].icon = p_icon; _changed_notify(p_column); + cached_minimum_size_dirty = true; } Ref<Texture2D> TreeItem::get_icon(int p_column) const { @@ -305,6 +339,7 @@ void TreeItem::set_icon_region(int p_column, const Rect2 &p_icon_region) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].icon_region = p_icon_region; _changed_notify(p_column); + cached_minimum_size_dirty = true; } Rect2 TreeItem::get_icon_region(int p_column) const { @@ -327,6 +362,7 @@ void TreeItem::set_icon_max_width(int p_column, int p_max) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].icon_max_w = p_max; _changed_notify(p_column); + cached_minimum_size_dirty = true; } int TreeItem::get_icon_max_width(int p_column) const { @@ -411,7 +447,7 @@ void TreeItem::set_collapsed(bool p_collapsed) { if (tree->select_mode == Tree::SELECT_MULTI) { tree->selected_item = this; - emit_signal("cell_selected"); + emit_signal(SNAME("cell_selected")); } else { select(tree->selected_col); } @@ -421,7 +457,7 @@ void TreeItem::set_collapsed(bool p_collapsed) { } _changed_notify(); - tree->emit_signal("item_collapsed", this); + tree->emit_signal(SNAME("item_collapsed"), this); } bool TreeItem::is_collapsed() { @@ -439,6 +475,7 @@ void TreeItem::uncollapse_tree() { void TreeItem::set_custom_minimum_height(int p_height) { custom_min_height = p_height; _changed_notify(); + cached_minimum_size_dirty = true; } int TreeItem::get_custom_minimum_height() const { @@ -451,6 +488,7 @@ TreeItem *TreeItem::create_child(int p_idx) { TreeItem *ti = memnew(TreeItem(tree)); if (tree) { ti->cells.resize(tree->columns.size()); + tree->update(); } TreeItem *l_prev = nullptr; @@ -654,11 +692,7 @@ void TreeItem::move_before(TreeItem *p_item) { next = p_item; p_item->prev = this; - if (old_tree && old_tree != tree) { - old_tree->update(); - } - - if (tree) { + if (tree && old_tree == tree) { tree->update(); } } @@ -696,11 +730,7 @@ void TreeItem::move_after(TreeItem *p_item) { parent->children_cache.append(this); } - if (old_tree && old_tree != tree) { - old_tree->update(); - } - - if (tree) { + if (tree && old_tree == tree) { tree->update(); } } @@ -770,6 +800,7 @@ void TreeItem::add_button(int p_column, const Ref<Texture2D> &p_button, int p_id button.tooltip = p_tooltip; cells.write[p_column].buttons.push_back(button); _changed_notify(p_column); + cached_minimum_size_dirty = true; } int TreeItem::get_button_count(int p_column) const { @@ -813,6 +844,7 @@ void TreeItem::set_button(int p_column, int p_idx, const Ref<Texture2D> &p_butto ERR_FAIL_INDEX(p_idx, cells[p_column].buttons.size()); cells.write[p_column].buttons.write[p_idx].texture = p_button; _changed_notify(p_column); + cached_minimum_size_dirty = true; } void TreeItem::set_button_color(int p_column, int p_idx, const Color &p_color) { @@ -828,6 +860,7 @@ void TreeItem::set_button_disabled(int p_column, int p_idx, bool p_disabled) { cells.write[p_column].buttons.write[p_idx].disabled = p_disabled; _changed_notify(p_column); + cached_minimum_size_dirty = true; } bool TreeItem::is_button_disabled(int p_column, int p_idx) const { @@ -841,6 +874,7 @@ void TreeItem::set_editable(int p_column, bool p_editable) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].editable = p_editable; _changed_notify(p_column); + cached_minimum_size_dirty = true; } bool TreeItem::is_editable(int p_column) { @@ -873,12 +907,25 @@ void TreeItem::clear_custom_color(int p_column) { void TreeItem::set_custom_font(int p_column, const Ref<Font> &p_font) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].custom_font = p_font; + cached_minimum_size_dirty = true; } + Ref<Font> TreeItem::get_custom_font(int p_column) const { ERR_FAIL_INDEX_V(p_column, cells.size(), Ref<Font>()); return cells[p_column].custom_font; } +void TreeItem::set_custom_font_size(int p_column, int p_font_size) { + ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].custom_font_size = p_font_size; + cached_minimum_size_dirty = true; +} + +int TreeItem::get_custom_font_size(int p_column) const { + ERR_FAIL_INDEX_V(p_column, cells.size(), -1); + return cells[p_column].custom_font_size; +} + void TreeItem::set_tooltip(int p_column, const String &p_tooltip) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].tooltip = p_tooltip; @@ -915,6 +962,7 @@ Color TreeItem::get_custom_bg_color(int p_column) const { void TreeItem::set_custom_as_button(int p_column, bool p_button) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].custom_button = p_button; + cached_minimum_size_dirty = true; } bool TreeItem::is_custom_set_as_button(int p_column) const { @@ -926,6 +974,7 @@ void TreeItem::set_text_align(int p_column, TextAlign p_align) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].text_align = p_align; _changed_notify(p_column); + cached_minimum_size_dirty = true; } TreeItem::TextAlign TreeItem::get_text_align(int p_column) const { @@ -937,6 +986,7 @@ void TreeItem::set_expand_right(int p_column, bool p_enable) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].expand_right = p_enable; _changed_notify(p_column); + cached_minimum_size_dirty = true; } bool TreeItem::get_expand_right(int p_column) const { @@ -947,6 +997,7 @@ bool TreeItem::get_expand_right(int p_column) const { void TreeItem::set_disable_folding(bool p_disable) { disable_folding = p_disable; _changed_notify(0); + cached_minimum_size_dirty = true; } bool TreeItem::is_folding_disabled() const { @@ -958,49 +1009,54 @@ Size2 TreeItem::get_minimum_size(int p_column) { Tree *tree = get_tree(); ERR_FAIL_COND_V(!tree, Size2()); - Size2 size; + if (cached_minimum_size_dirty) { + Size2 size; - // Default offset? - //size.width += (disable_folding || tree->hide_folding) ? tree->cache.hseparation : tree->cache.item_margin; + // Default offset? + //size.width += (disable_folding || tree->hide_folding) ? tree->cache.hseparation : tree->cache.item_margin; - // Text. - const TreeItem::Cell &cell = cells[p_column]; - if (!cell.text.is_empty()) { - if (cell.dirty) { - tree->update_item_cell(this, p_column); + // Text. + const TreeItem::Cell &cell = cells[p_column]; + if (!cell.text.is_empty()) { + if (cell.dirty) { + tree->update_item_cell(this, p_column); + } + Size2 text_size = cell.text_buf->get_size(); + size.width += text_size.width; + size.height = MAX(size.height, text_size.height); } - Size2 text_size = cell.text_buf->get_size(); - size.width += text_size.width; - size.height = MAX(size.height, text_size.height); - } - // Icon. - if (cell.mode == CELL_MODE_CHECK) { - size.width += tree->cache.checked->get_width() + tree->cache.hseparation; - } - if (cell.icon.is_valid()) { - Size2i icon_size = cell.get_icon_size(); - if (cell.icon_max_w > 0 && icon_size.width > cell.icon_max_w) { - icon_size.width = cell.icon_max_w; + // Icon. + if (cell.mode == CELL_MODE_CHECK) { + size.width += tree->cache.checked->get_width() + tree->cache.hseparation; + } + if (cell.icon.is_valid()) { + Size2i icon_size = cell.get_icon_size(); + if (cell.icon_max_w > 0 && icon_size.width > cell.icon_max_w) { + icon_size.width = cell.icon_max_w; + } + size.width += icon_size.width + tree->cache.hseparation; + size.height = MAX(size.height, icon_size.height); } - size.width += icon_size.width + tree->cache.hseparation; - size.height = MAX(size.height, icon_size.height); - } - // Buttons. - for (int i = 0; i < cell.buttons.size(); i++) { - Ref<Texture2D> texture = cell.buttons[i].texture; - if (texture.is_valid()) { - Size2 button_size = texture->get_size() + tree->cache.button_pressed->get_minimum_size(); - size.width += button_size.width; - size.height = MAX(size.height, button_size.height); + // Buttons. + for (int i = 0; i < cell.buttons.size(); i++) { + Ref<Texture2D> texture = cell.buttons[i].texture; + if (texture.is_valid()) { + Size2 button_size = texture->get_size() + tree->cache.button_pressed->get_minimum_size(); + size.width += button_size.width; + size.height = MAX(size.height, button_size.height); + } } - } - if (cell.buttons.size() >= 2) { - size.width += (cell.buttons.size() - 1) * tree->cache.button_margin; + if (cell.buttons.size() >= 2) { + size.width += (cell.buttons.size() - 1) * tree->cache.button_margin; + } + + cached_minimum_size = size; + cached_minimum_size_dirty = false; } - return size; + return cached_minimum_size; } Variant TreeItem::_call_recursive_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { @@ -1044,7 +1100,9 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("get_cell_mode", "column"), &TreeItem::get_cell_mode); ClassDB::bind_method(D_METHOD("set_checked", "column", "checked"), &TreeItem::set_checked); + ClassDB::bind_method(D_METHOD("set_indeterminate", "column", "indeterminate"), &TreeItem::set_indeterminate); ClassDB::bind_method(D_METHOD("is_checked", "column"), &TreeItem::is_checked); + ClassDB::bind_method(D_METHOD("is_indeterminate", "column"), &TreeItem::is_indeterminate); ClassDB::bind_method(D_METHOD("set_text", "column", "text"), &TreeItem::set_text); ClassDB::bind_method(D_METHOD("get_text", "column"), &TreeItem::get_text); @@ -1115,6 +1173,9 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_custom_font", "column", "font"), &TreeItem::set_custom_font); ClassDB::bind_method(D_METHOD("get_custom_font", "column"), &TreeItem::get_custom_font); + ClassDB::bind_method(D_METHOD("set_custom_font_size", "column", "font_size"), &TreeItem::set_custom_font_size); + ClassDB::bind_method(D_METHOD("get_custom_font_size", "column"), &TreeItem::get_custom_font_size); + ClassDB::bind_method(D_METHOD("set_custom_bg_color", "column", "color", "just_outline"), &TreeItem::set_custom_bg_color, DEFVAL(false)); ClassDB::bind_method(D_METHOD("clear_custom_bg_color", "column"), &TreeItem::clear_custom_bg_color); ClassDB::bind_method(D_METHOD("get_custom_bg_color", "column"), &TreeItem::get_custom_bg_color); @@ -1217,64 +1278,69 @@ TreeItem::~TreeItem() { /**********************************************/ void Tree::update_cache() { - cache.font = get_theme_font("font"); - cache.font_size = get_theme_font_size("font_size"); - cache.tb_font = get_theme_font("title_button_font"); - cache.tb_font_size = get_theme_font_size("title_button_font_size"); - cache.bg = get_theme_stylebox("bg"); - cache.selected = get_theme_stylebox("selected"); - cache.selected_focus = get_theme_stylebox("selected_focus"); - cache.cursor = get_theme_stylebox("cursor"); - cache.cursor_unfocus = get_theme_stylebox("cursor_unfocused"); - cache.button_pressed = get_theme_stylebox("button_pressed"); - - cache.checked = get_theme_icon("checked"); - cache.unchecked = get_theme_icon("unchecked"); + cache.font = get_theme_font(SNAME("font")); + cache.font_size = get_theme_font_size(SNAME("font_size")); + cache.tb_font = get_theme_font(SNAME("title_button_font")); + cache.tb_font_size = get_theme_font_size(SNAME("title_button_font_size")); + cache.bg = get_theme_stylebox(SNAME("bg")); + cache.selected = get_theme_stylebox(SNAME("selected")); + cache.selected_focus = get_theme_stylebox(SNAME("selected_focus")); + cache.cursor = get_theme_stylebox(SNAME("cursor")); + cache.cursor_unfocus = get_theme_stylebox(SNAME("cursor_unfocused")); + cache.button_pressed = get_theme_stylebox(SNAME("button_pressed")); + + cache.checked = get_theme_icon(SNAME("checked")); + cache.unchecked = get_theme_icon(SNAME("unchecked")); + cache.indeterminate = get_theme_icon(SNAME("indeterminate")); if (is_layout_rtl()) { - cache.arrow_collapsed = get_theme_icon("arrow_collapsed_mirrored"); + cache.arrow_collapsed = get_theme_icon(SNAME("arrow_collapsed_mirrored")); } else { - cache.arrow_collapsed = get_theme_icon("arrow_collapsed"); - } - cache.arrow = get_theme_icon("arrow"); - cache.select_arrow = get_theme_icon("select_arrow"); - cache.updown = get_theme_icon("updown"); - - cache.custom_button = get_theme_stylebox("custom_button"); - cache.custom_button_hover = get_theme_stylebox("custom_button_hover"); - cache.custom_button_pressed = get_theme_stylebox("custom_button_pressed"); - cache.custom_button_font_highlight = get_theme_color("custom_button_font_highlight"); - - cache.font_color = get_theme_color("font_color"); - cache.font_selected_color = get_theme_color("font_selected_color"); - cache.drop_position_color = get_theme_color("drop_position_color"); - cache.hseparation = get_theme_constant("hseparation"); - cache.vseparation = get_theme_constant("vseparation"); - cache.item_margin = get_theme_constant("item_margin"); - cache.button_margin = get_theme_constant("button_margin"); - - cache.font_outline_color = get_theme_color("font_outline_color"); - cache.font_outline_size = get_theme_constant("outline_size"); - - cache.draw_guides = get_theme_constant("draw_guides"); - cache.guide_color = get_theme_color("guide_color"); - cache.draw_relationship_lines = get_theme_constant("draw_relationship_lines"); - cache.relationship_line_width = get_theme_constant("relationship_line_width"); - cache.parent_hl_line_width = get_theme_constant("parent_hl_line_width"); - cache.children_hl_line_width = get_theme_constant("children_hl_line_width"); - cache.parent_hl_line_margin = get_theme_constant("parent_hl_line_margin"); - cache.relationship_line_color = get_theme_color("relationship_line_color"); - cache.parent_hl_line_color = get_theme_color("parent_hl_line_color"); - cache.children_hl_line_color = get_theme_color("children_hl_line_color"); - - cache.scroll_border = get_theme_constant("scroll_border"); - cache.scroll_speed = get_theme_constant("scroll_speed"); - - cache.title_button = get_theme_stylebox("title_button_normal"); - cache.title_button_pressed = get_theme_stylebox("title_button_pressed"); - cache.title_button_hover = get_theme_stylebox("title_button_hover"); - cache.title_button_color = get_theme_color("title_button_color"); + cache.arrow_collapsed = get_theme_icon(SNAME("arrow_collapsed")); + } + cache.arrow = get_theme_icon(SNAME("arrow")); + cache.select_arrow = get_theme_icon(SNAME("select_arrow")); + cache.updown = get_theme_icon(SNAME("updown")); + + cache.custom_button = get_theme_stylebox(SNAME("custom_button")); + cache.custom_button_hover = get_theme_stylebox(SNAME("custom_button_hover")); + cache.custom_button_pressed = get_theme_stylebox(SNAME("custom_button_pressed")); + cache.custom_button_font_highlight = get_theme_color(SNAME("custom_button_font_highlight")); + + 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.item_margin = get_theme_constant(SNAME("item_margin")); + cache.button_margin = get_theme_constant(SNAME("button_margin")); + + cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); + cache.font_outline_size = get_theme_constant(SNAME("outline_size")); + + cache.draw_guides = get_theme_constant(SNAME("draw_guides")); + cache.guide_color = get_theme_color(SNAME("guide_color")); + cache.draw_relationship_lines = get_theme_constant(SNAME("draw_relationship_lines")); + cache.relationship_line_width = get_theme_constant(SNAME("relationship_line_width")); + cache.parent_hl_line_width = get_theme_constant(SNAME("parent_hl_line_width")); + cache.children_hl_line_width = get_theme_constant(SNAME("children_hl_line_width")); + cache.parent_hl_line_margin = get_theme_constant(SNAME("parent_hl_line_margin")); + cache.relationship_line_color = get_theme_color(SNAME("relationship_line_color")); + cache.parent_hl_line_color = get_theme_color(SNAME("parent_hl_line_color")); + cache.children_hl_line_color = get_theme_color(SNAME("children_hl_line_color")); + + cache.scroll_border = get_theme_constant(SNAME("scroll_border")); + cache.scroll_speed = get_theme_constant(SNAME("scroll_speed")); + + cache.title_button = get_theme_stylebox(SNAME("title_button_normal")); + cache.title_button_pressed = get_theme_stylebox(SNAME("title_button_pressed")); + cache.title_button_hover = get_theme_stylebox(SNAME("title_button_hover")); + cache.title_button_color = get_theme_color(SNAME("title_button_color")); v_scroll->set_custom_step(cache.font->get_height(cache.font_size)); + + for (TreeItem *item = get_root(); item; item = item->get_next()) { + item->cached_minimum_size_dirty = true; + } } int Tree::compute_item_height(TreeItem *p_item) const { @@ -1489,7 +1555,14 @@ void Tree::update_item_cell(TreeItem *p_item, int p_col) { } else { font = cache.font; } - p_item->cells.write[p_col].text_buf->add_string(valtext, font, cache.font_size, p_item->cells[p_col].opentype_features, (p_item->cells[p_col].language != "") ? p_item->cells[p_col].language : TranslationServer::get_singleton()->get_tool_locale()); + + int font_size; + if (p_item->cells[p_col].custom_font_size > 0) { + font_size = p_item->cells[p_col].custom_font_size; + } 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 != "") ? p_item->cells[p_col].language : TranslationServer::get_singleton()->get_tool_locale()); 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; } @@ -1678,24 +1751,30 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 } } - if (drop_mode_flags && drop_mode_over == p_item) { + if (drop_mode_flags && drop_mode_over) { Rect2 r = cell_rect; - bool has_parent = p_item->get_first_child() != nullptr; if (rtl) { r.position.x = get_size().width - r.position.x - r.size.x; } - - if (drop_mode_section == -1 || has_parent || drop_mode_section == 0) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, r.size.x, 1), cache.drop_position_color); - } - - if (drop_mode_section == 0) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, 1, r.size.y), cache.drop_position_color); - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x + r.size.x - 1, r.position.y, 1, r.size.y), cache.drop_position_color); - } - - if ((drop_mode_section == 1 && !has_parent) || drop_mode_section == 0) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y + r.size.y, r.size.x, 1), cache.drop_position_color); + if (drop_mode_over == p_item) { + if (drop_mode_section == 0 || drop_mode_section == -1) { + // Line above. + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, r.size.x, 1), cache.drop_position_color); + } + if (drop_mode_section == 0) { + // Side lines. + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, 1, r.size.y), cache.drop_position_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x + r.size.x - 1, r.position.y, 1, r.size.y), cache.drop_position_color); + } + if (drop_mode_section == 0 || (drop_mode_section == 1 && (!p_item->get_first_child() || p_item->is_collapsed()))) { + // Line below. + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y + r.size.y, r.size.x, 1), cache.drop_position_color); + } + } else if (drop_mode_over == p_item->get_parent()) { + if (drop_mode_section == 1 && !p_item->get_prev() /* && !drop_mode_over->is_collapsed() */) { // The drop_mode_over shouldn't ever be collapsed in here, otherwise we would be drawing a child of a collapsed item. + // Line above. + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, r.size.x, 1), cache.drop_position_color); + } } } @@ -1723,10 +1802,13 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 case TreeItem::CELL_MODE_CHECK: { Ref<Texture2D> checked = cache.checked; Ref<Texture2D> unchecked = cache.unchecked; + Ref<Texture2D> indeterminate = cache.indeterminate; Point2i check_ofs = item_rect.position; check_ofs.y += Math::floor((real_t)(item_rect.size.y - checked->get_height()) / 2); - if (p_item->cells[i].checked) { + if (p_item->cells[i].indeterminate) { + indeterminate->draw(ci, check_ofs); + } else if (p_item->cells[i].checked) { checked->draw(ci, check_ofs); } else { unchecked->draw(ci, check_ofs); @@ -2077,7 +2159,7 @@ void Tree::select_single_item(TreeItem *p_selected, TreeItem *p_current, int p_c selected_item = p_selected; selected_col = 0; if (!emitted_row) { - emit_signal("item_selected"); + emit_signal(SNAME("item_selected")); emitted_row = true; } /* @@ -2097,28 +2179,28 @@ void Tree::select_single_item(TreeItem *p_selected, TreeItem *p_current, int p_c selected_item = p_selected; selected_col = i; - emit_signal("cell_selected"); + emit_signal(SNAME("cell_selected")); if (select_mode == SELECT_MULTI) { - emit_signal("multi_selected", p_current, i, true); + emit_signal(SNAME("multi_selected"), p_current, i, true); } else if (select_mode == SELECT_SINGLE) { - emit_signal("item_selected"); + emit_signal(SNAME("item_selected")); } } else if (select_mode == SELECT_MULTI && (selected_item != p_selected || selected_col != i)) { selected_item = p_selected; selected_col = i; - emit_signal("cell_selected"); + emit_signal(SNAME("cell_selected")); } } else { if (r_in_range && *r_in_range && !p_force_deselect) { if (!c.selected && c.selectable) { c.selected = true; - emit_signal("multi_selected", p_current, i, true); + emit_signal(SNAME("multi_selected"), p_current, i, true); } } else if (!r_in_range || p_force_deselect) { if (select_mode == SELECT_MULTI && c.selected) { - emit_signal("multi_selected", p_current, i, false); + emit_signal(SNAME("multi_selected"), p_current, i, false); } c.selected = false; } @@ -2155,6 +2237,10 @@ void Tree::_range_click_timeout() { } } + if (!root) { + return; + } + click_handled = false; Ref<InputEventMouseButton> mb; mb.instantiate(); @@ -2182,7 +2268,7 @@ void Tree::_range_click_timeout() { } if (propagate_mouse_activated) { - emit_signal("item_activated"); + emit_signal(SNAME("item_activated")); propagate_mouse_activated = false; } @@ -2287,15 +2373,24 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int cache.click_type = Cache::CLICK_NONE; return -1; } + + // Make sure the click is correct. + Point2 click_pos = get_global_mouse_position() - get_global_position(); + if (!get_item_at_position(click_pos)) { + pressed_button = -1; + cache.click_type = Cache::CLICK_NONE; + return -1; + } + pressed_button = j; cache.click_type = Cache::CLICK_BUTTON; cache.click_index = j; cache.click_id = c.buttons[j].id; cache.click_item = p_item; cache.click_column = col; - cache.click_pos = get_global_mouse_position() - get_global_position(); + cache.click_pos = click_pos; update(); - //emit_signal("button_pressed"); + //emit_signal(SNAME("button_pressed")); return -1; } @@ -2316,15 +2411,15 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int if (select_mode == SELECT_MULTI && p_mod->is_command_pressed() && c.selectable) { if (!c.selected || p_button == MOUSE_BUTTON_RIGHT) { p_item->select(col); - emit_signal("multi_selected", p_item, col, true); + emit_signal(SNAME("multi_selected"), p_item, col, true); if (p_button == MOUSE_BUTTON_RIGHT) { - emit_signal("item_rmb_selected", get_local_mouse_position()); + emit_signal(SNAME("item_rmb_selected"), get_local_mouse_position()); } //p_item->selected_signal.call(col); } else { p_item->deselect(col); - emit_signal("multi_selected", p_item, col, false); + emit_signal(SNAME("multi_selected"), p_item, col, false); //p_item->deselected_signal.call(col); } @@ -2335,7 +2430,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int select_single_item(p_item, root, col, selected_item, &inrange); if (p_button == MOUSE_BUTTON_RIGHT) { - emit_signal("item_rmb_selected", get_local_mouse_position()); + emit_signal(SNAME("item_rmb_selected"), get_local_mouse_position()); } } else { int icount = _count_selected_items(root); @@ -2349,14 +2444,14 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int } if (p_button == MOUSE_BUTTON_RIGHT) { - emit_signal("item_rmb_selected", get_local_mouse_position()); + emit_signal(SNAME("item_rmb_selected"), get_local_mouse_position()); } } } /* if (!c.selected && select_mode==SELECT_MULTI) { - emit_signal("multi_selected",p_item,col,true); + emit_signal(SNAME("multi_selected"),p_item,col,true); } */ update(); @@ -2470,7 +2565,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int custom_popup_rect = Rect2i(get_global_position() + Point2i(col_ofs, _get_title_button_height() + y_ofs + item_h - cache.offset.y), Size2(get_column_width(col), item_h)); if (on_arrow || !p_item->cells[col].custom_button) { - emit_signal("custom_popup_edited", ((bool)(x >= (col_width - item_h / 2)))); + emit_signal(SNAME("custom_popup_edited"), ((bool)(x >= (col_width - item_h / 2)))); } if (!p_item->cells[col].custom_button || !on_arrow) { @@ -2522,7 +2617,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int } } if (p_item == root && p_button == MOUSE_BUTTON_RIGHT) { - emit_signal("empty_rmb", get_local_mouse_position()); + emit_signal(SNAME("empty_rmb"), get_local_mouse_position()); } } @@ -2628,7 +2723,7 @@ void Tree::_go_left() { } else { if (select_mode == SELECT_MULTI) { selected_col--; - emit_signal("cell_selected"); + emit_signal(SNAME("cell_selected")); } else { selected_item->select(selected_col - 1); } @@ -2649,7 +2744,7 @@ void Tree::_go_right() { } else { if (select_mode == SELECT_MULTI) { selected_col++; - emit_signal("cell_selected"); + emit_signal(SNAME("cell_selected")); } else { selected_item->select(selected_col + 1); } @@ -2682,7 +2777,7 @@ void Tree::_go_up() { return; } selected_item = prev; - emit_signal("cell_selected"); + emit_signal(SNAME("cell_selected")); update(); } else { int col = selected_col < 0 ? 0 : selected_col; @@ -2725,7 +2820,7 @@ void Tree::_go_down() { } selected_item = next; - emit_signal("cell_selected"); + emit_signal(SNAME("cell_selected")); update(); } else { int col = selected_col < 0 ? 0 : selected_col; @@ -2743,7 +2838,7 @@ void Tree::_go_down() { accept_event(); } -void Tree::_gui_input(Ref<InputEvent> p_event) { +void Tree::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventKey> k = p_event; @@ -2826,7 +2921,7 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { if (select_mode == SELECT_MULTI) { selected_item = next; - emit_signal("cell_selected"); + emit_signal(SNAME("cell_selected")); update(); } else { while (next && !next->cells[selected_col].selectable) { @@ -2864,7 +2959,7 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { if (select_mode == SELECT_MULTI) { selected_item = prev; - emit_signal("cell_selected"); + emit_signal(SNAME("cell_selected")); update(); } else { while (prev && !prev->cells[selected_col].selectable) { @@ -2880,7 +2975,7 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { if (selected_item) { //bring up editor if possible if (!edit_selected()) { - emit_signal("item_activated"); + emit_signal(SNAME("item_activated")); incr_search.clear(); } } @@ -2892,10 +2987,10 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { } if (selected_item->is_selected(selected_col)) { selected_item->deselect(selected_col); - emit_signal("multi_selected", selected_item, selected_col, false); + emit_signal(SNAME("multi_selected"), selected_item, selected_col, false); } else if (selected_item->is_selectable(selected_col)) { selected_item->select(selected_col); - emit_signal("multi_selected", selected_item, selected_col, true); + emit_signal(SNAME("multi_selected"), selected_item, selected_col, true); } } accept_event(); @@ -3079,7 +3174,7 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { for (int i = 0; i < columns.size(); i++) { len += get_column_width(i); if (pos.x < len) { - emit_signal("column_title_pressed", i); + emit_signal(SNAME("column_title_pressed"), i); break; } } @@ -3106,10 +3201,10 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { } if (rect.has_point(mpos)) { if (!edit_selected()) { - emit_signal("item_double_clicked"); + emit_signal(SNAME("item_double_clicked")); } } else { - emit_signal("item_double_clicked"); + emit_signal(SNAME("item_double_clicked")); } } pressing_for_editor = false; @@ -3118,7 +3213,7 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { 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_pressed", cache.click_item, cache.click_column, cache.click_id); + emit_signal(SNAME("button_pressed"), cache.click_item, cache.click_column, cache.click_id); } cache.click_type = Cache::CLICK_NONE; cache.click_index = -1; @@ -3178,7 +3273,7 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { } if (!root || (!root->get_first_child() && hide_root)) { if (b->get_button_index() == MOUSE_BUTTON_RIGHT && allow_rmb_select) { - emit_signal("empty_tree_rmb_selected", get_local_mouse_position()); + emit_signal(SNAME("empty_tree_rmb_selected"), get_local_mouse_position()); } break; } @@ -3229,13 +3324,13 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { if (b->get_button_index() == MOUSE_BUTTON_LEFT) { if (get_item_at_position(b->get_position()) == nullptr && !b->is_shift_pressed() && !b->is_ctrl_pressed() && !b->is_command_pressed()) { - emit_signal("nothing_selected"); + emit_signal(SNAME("nothing_selected")); } } } if (propagate_mouse_activated) { - emit_signal("item_activated"); + emit_signal(SNAME("item_activated")); propagate_mouse_activated = false; } @@ -3304,7 +3399,7 @@ bool Tree::edit_selected() { edited_item = s; edited_col = col; custom_popup_rect = Rect2i(get_global_position() + rect.position, rect.size); - emit_signal("custom_popup_edited", false); + emit_signal(SNAME("custom_popup_edited"), false); item_edited(col, s); return true; @@ -3540,8 +3635,8 @@ void Tree::_notification(int p_what) { RID ci = get_canvas_item(); Ref<StyleBox> bg = cache.bg; - Color font_outline_color = get_theme_color("font_outline_color"); - int outline_size = get_theme_constant("outline_size"); + Color font_outline_color = get_theme_color(SNAME("font_outline_color")); + int outline_size = get_theme_constant(SNAME("outline_size")); Point2 draw_ofs; draw_ofs += bg->get_offset(); @@ -3591,7 +3686,7 @@ void Tree::_notification(int p_what) { // Otherwise, section heading backgrounds can appear to be in front of the focus outline when scrolling. if (has_focus()) { RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, true); - const Ref<StyleBox> bg_focus = get_theme_stylebox("bg_focus"); + const Ref<StyleBox> bg_focus = get_theme_stylebox(SNAME("bg_focus")); bg_focus->draw(ci, Rect2(Point2(), get_size())); RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false); } @@ -3692,9 +3787,9 @@ void Tree::item_edited(int p_column, TreeItem *p_item, bool p_lmb) { edited_item->cells.write[p_column].dirty = true; } if (p_lmb) { - emit_signal("item_edited"); + emit_signal(SNAME("item_edited")); } else { - emit_signal("item_rmb_edited"); + emit_signal(SNAME("item_rmb_edited")); } } @@ -3712,7 +3807,7 @@ void Tree::item_selected(int p_column, TreeItem *p_item) { } p_item->cells.write[p_column].selected = true; - //emit_signal("multi_selected",p_item,p_column,true); - NO this is for TreeItem::select + //emit_signal(SNAME("multi_selected"),p_item,p_column,true); - NO this is for TreeItem::select selected_col = p_column; if (!selected_item) { @@ -4073,7 +4168,7 @@ void Tree::ensure_cursor_is_visible() { if (cell_h > screen_h) { // Screen size is too small, maybe it was not resized yet. v_scroll->set_value(y_offset); } else if (y_offset + cell_h > v_scroll->get_value() + screen_h) { - v_scroll->call_deferred("set_value", y_offset - screen_h + cell_h); + v_scroll->call_deferred(SNAME("set_value"), y_offset - screen_h + cell_h); } else if (y_offset < v_scroll->get_value()) { v_scroll->set_value(y_offset); } @@ -4091,7 +4186,7 @@ void Tree::ensure_cursor_is_visible() { if (cell_w > screen_w) { h_scroll->set_value(x_offset); } else if (x_offset + cell_w > h_scroll->get_value() + screen_w) { - h_scroll->call_deferred("set_value", x_offset - screen_w + cell_w); + h_scroll->call_deferred(SNAME("set_value"), x_offset - screen_w + cell_w); } else if (x_offset < h_scroll->get_value()) { h_scroll->set_value(x_offset); } @@ -4625,8 +4720,6 @@ bool Tree::get_allow_reselect() const { } void Tree::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &Tree::_gui_input); - ClassDB::bind_method(D_METHOD("clear"), &Tree::clear); ClassDB::bind_method(D_METHOD("create_item", "parent", "idx"), &Tree::_create_item, DEFVAL(Variant()), DEFVAL(-1)); @@ -4745,12 +4838,11 @@ Tree::Tree() { popup_menu = memnew(PopupMenu); popup_menu->hide(); - add_child(popup_menu); - // popup_menu->set_as_top_level(true); + add_child(popup_menu, false, INTERNAL_MODE_FRONT); popup_editor = memnew(Popup); popup_editor->set_wrap_controls(true); - add_child(popup_editor); + add_child(popup_editor, false, INTERNAL_MODE_FRONT); popup_editor_vb = memnew(VBoxContainer); popup_editor->add_child(popup_editor_vb); popup_editor_vb->add_theme_constant_override("separation", 0); @@ -4768,12 +4860,12 @@ Tree::Tree() { h_scroll = memnew(HScrollBar); v_scroll = memnew(VScrollBar); - add_child(h_scroll); - add_child(v_scroll); + add_child(h_scroll, false, INTERNAL_MODE_FRONT); + add_child(v_scroll, false, INTERNAL_MODE_FRONT); range_click_timer = memnew(Timer); range_click_timer->connect("timeout", callable_mp(this, &Tree::_range_click_timeout)); - add_child(range_click_timer); + add_child(range_click_timer, false, INTERNAL_MODE_FRONT); h_scroll->connect("value_changed", callable_mp(this, &Tree::_scroll_moved)); v_scroll->connect("value_changed", callable_mp(this, &Tree::_scroll_moved)); diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 10e6642303..85fed941dc 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -82,6 +82,7 @@ private: int icon_max_w = 0; bool expr = false; bool checked = false; + bool indeterminate = false; bool editable = false; bool selected = false; bool selectable = true; @@ -113,6 +114,7 @@ private: Vector<Button> buttons; Ref<Font> custom_font; + int custom_font_size = -1; Cell() { text_buf.instantiate(); @@ -128,6 +130,9 @@ private: bool disable_folding = false; int custom_min_height = 0; + Size2i cached_minimum_size; + bool cached_minimum_size_dirty = true; + TreeItem *parent = nullptr; // parent item TreeItem *prev = nullptr; // previous in list TreeItem *next = nullptr; // next in list @@ -209,7 +214,9 @@ public: /* check mode */ void set_checked(int p_column, bool p_checked); + void set_indeterminate(int p_column, bool p_indeterminate); bool is_checked(int p_column) const; + bool is_indeterminate(int p_column) const; void set_text(int p_column, String p_text); String get_text(int p_column) const; @@ -296,6 +303,9 @@ public: void set_custom_font(int p_column, const Ref<Font> &p_font); Ref<Font> get_custom_font(int p_column) const; + void set_custom_font_size(int p_column, int p_font_size); + int get_custom_font_size(int p_column) const; + void set_custom_bg_color(int p_column, const Color &p_color, bool p_bg_outline = false); void clear_custom_bg_color(int p_column); Color get_custom_bg_color(int p_column) const; @@ -459,11 +469,8 @@ private: void popup_select(int p_option); - void _gui_input(Ref<InputEvent> p_event); void _notification(int p_what); - Size2 get_minimum_size() const override; - void item_edited(int p_column, TreeItem *p_item, bool p_lmb = true); void item_changed(int p_column, TreeItem *p_item); void item_selected(int p_column, TreeItem *p_item); @@ -493,6 +500,7 @@ private: Ref<Texture2D> checked; Ref<Texture2D> unchecked; + Ref<Texture2D> indeterminate; Ref<Texture2D> arrow_collapsed; Ref<Texture2D> arrow; Ref<Texture2D> select_arrow; @@ -624,6 +632,8 @@ protected: } public: + virtual void gui_input(const Ref<InputEvent> &p_event) override; + virtual String get_tooltip(const Point2 &p_pos) const override; TreeItem *get_item_at_position(const Point2 &p_pos) const; @@ -721,6 +731,8 @@ public: void set_allow_reselect(bool p_allow); bool get_allow_reselect() const; + Size2 get_minimum_size() const override; + Tree(); ~Tree(); }; diff --git a/scene/gui/video_player.cpp b/scene/gui/video_player.cpp index 229b5384ea..8734037a57 100644 --- a/scene/gui/video_player.cpp +++ b/scene/gui/video_player.cpp @@ -129,7 +129,7 @@ void VideoPlayer::_mix_audio() { void VideoPlayer::_notification(int p_notification) { switch (p_notification) { case NOTIFICATION_ENTER_TREE: { - AudioServer::get_singleton()->add_callback(_mix_audios, this); + AudioServer::get_singleton()->add_mix_callback(_mix_audios, this); if (stream.is_valid() && autoplay && !Engine::get_singleton()->is_editor_hint()) { play(); @@ -138,8 +138,7 @@ void VideoPlayer::_notification(int p_notification) { } break; case NOTIFICATION_EXIT_TREE: { - AudioServer::get_singleton()->remove_callback(_mix_audios, this); - + AudioServer::get_singleton()->remove_mix_callback(_mix_audios, this); } break; case NOTIFICATION_INTERNAL_PROCESS: { diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp index db55f4feb7..64b169b1fb 100644 --- a/scene/main/canvas_item.cpp +++ b/scene/main/canvas_item.cpp @@ -30,292 +30,17 @@ #include "canvas_item.h" -#include "core/input/input.h" #include "core/object/message_queue.h" #include "scene/2d/canvas_group.h" #include "scene/main/canvas_layer.h" -#include "scene/main/viewport.h" #include "scene/main/window.h" +#include "scene/resources/canvas_item_material.h" #include "scene/resources/font.h" +#include "scene/resources/multimesh.h" #include "scene/resources/style_box.h" -#include "scene/resources/texture.h" +#include "scene/resources/world_2d.h" #include "scene/scene_string_names.h" -#include "servers/rendering_server.h" -Mutex CanvasItemMaterial::material_mutex; -SelfList<CanvasItemMaterial>::List *CanvasItemMaterial::dirty_materials = nullptr; -Map<CanvasItemMaterial::MaterialKey, CanvasItemMaterial::ShaderData> CanvasItemMaterial::shader_map; -CanvasItemMaterial::ShaderNames *CanvasItemMaterial::shader_names = nullptr; - -void CanvasItemMaterial::init_shaders() { - dirty_materials = memnew(SelfList<CanvasItemMaterial>::List); - - shader_names = memnew(ShaderNames); - - shader_names->particles_anim_h_frames = "particles_anim_h_frames"; - shader_names->particles_anim_v_frames = "particles_anim_v_frames"; - shader_names->particles_anim_loop = "particles_anim_loop"; -} - -void CanvasItemMaterial::finish_shaders() { - memdelete(dirty_materials); - memdelete(shader_names); - dirty_materials = nullptr; -} - -void CanvasItemMaterial::_update_shader() { - dirty_materials->remove(&element); - - MaterialKey mk = _compute_key(); - if (mk.key == current_key.key) { - return; //no update required in the end - } - - if (shader_map.has(current_key)) { - shader_map[current_key].users--; - if (shader_map[current_key].users == 0) { - //deallocate shader, as it's no longer in use - RS::get_singleton()->free(shader_map[current_key].shader); - shader_map.erase(current_key); - } - } - - current_key = mk; - - if (shader_map.has(mk)) { - RS::get_singleton()->material_set_shader(_get_material(), shader_map[mk].shader); - shader_map[mk].users++; - return; - } - - //must create a shader! - - String code = "shader_type canvas_item;\nrender_mode "; - switch (blend_mode) { - case BLEND_MODE_MIX: - code += "blend_mix"; - break; - case BLEND_MODE_ADD: - code += "blend_add"; - break; - case BLEND_MODE_SUB: - code += "blend_sub"; - break; - case BLEND_MODE_MUL: - code += "blend_mul"; - break; - case BLEND_MODE_PREMULT_ALPHA: - code += "blend_premul_alpha"; - break; - case BLEND_MODE_DISABLED: - code += "blend_disabled"; - break; - } - - switch (light_mode) { - case LIGHT_MODE_NORMAL: - break; - case LIGHT_MODE_UNSHADED: - code += ",unshaded"; - break; - case LIGHT_MODE_LIGHT_ONLY: - code += ",light_only"; - break; - } - - code += ";\n"; - - if (particles_animation) { - code += "uniform int particles_anim_h_frames;\n"; - code += "uniform int particles_anim_v_frames;\n"; - code += "uniform bool particles_anim_loop;\n"; - - code += "void vertex() {\n"; - - code += "\tfloat h_frames = float(particles_anim_h_frames);\n"; - code += "\tfloat v_frames = float(particles_anim_v_frames);\n"; - - code += "\tVERTEX.xy /= vec2(h_frames, v_frames);\n"; - - code += "\tfloat particle_total_frames = float(particles_anim_h_frames * particles_anim_v_frames);\n"; - code += "\tfloat particle_frame = floor(INSTANCE_CUSTOM.z * float(particle_total_frames));\n"; - code += "\tif (!particles_anim_loop) {\n"; - code += "\t\tparticle_frame = clamp(particle_frame, 0.0, particle_total_frames - 1.0);\n"; - code += "\t} else {\n"; - code += "\t\tparticle_frame = mod(particle_frame, particle_total_frames);\n"; - code += "\t}"; - code += "\tUV /= vec2(h_frames, v_frames);\n"; - code += "\tUV += vec2(mod(particle_frame, h_frames) / h_frames, floor(particle_frame / h_frames) / v_frames);\n"; - code += "}\n"; - } - - ShaderData shader_data; - shader_data.shader = RS::get_singleton()->shader_create(); - shader_data.users = 1; - - RS::get_singleton()->shader_set_code(shader_data.shader, code); - - shader_map[mk] = shader_data; - - RS::get_singleton()->material_set_shader(_get_material(), shader_data.shader); -} - -void CanvasItemMaterial::flush_changes() { - MutexLock lock(material_mutex); - - while (dirty_materials->first()) { - dirty_materials->first()->self()->_update_shader(); - } -} - -void CanvasItemMaterial::_queue_shader_change() { - MutexLock lock(material_mutex); - - if (!element.in_list()) { - dirty_materials->add(&element); - } -} - -bool CanvasItemMaterial::_is_shader_dirty() const { - MutexLock lock(material_mutex); - - return element.in_list(); -} - -void CanvasItemMaterial::set_blend_mode(BlendMode p_blend_mode) { - blend_mode = p_blend_mode; - _queue_shader_change(); -} - -CanvasItemMaterial::BlendMode CanvasItemMaterial::get_blend_mode() const { - return blend_mode; -} - -void CanvasItemMaterial::set_light_mode(LightMode p_light_mode) { - light_mode = p_light_mode; - _queue_shader_change(); -} - -CanvasItemMaterial::LightMode CanvasItemMaterial::get_light_mode() const { - return light_mode; -} - -void CanvasItemMaterial::set_particles_animation(bool p_particles_anim) { - particles_animation = p_particles_anim; - _queue_shader_change(); - notify_property_list_changed(); -} - -bool CanvasItemMaterial::get_particles_animation() const { - return particles_animation; -} - -void CanvasItemMaterial::set_particles_anim_h_frames(int p_frames) { - particles_anim_h_frames = p_frames; - RS::get_singleton()->material_set_param(_get_material(), shader_names->particles_anim_h_frames, p_frames); -} - -int CanvasItemMaterial::get_particles_anim_h_frames() const { - return particles_anim_h_frames; -} - -void CanvasItemMaterial::set_particles_anim_v_frames(int p_frames) { - particles_anim_v_frames = p_frames; - RS::get_singleton()->material_set_param(_get_material(), shader_names->particles_anim_v_frames, p_frames); -} - -int CanvasItemMaterial::get_particles_anim_v_frames() const { - return particles_anim_v_frames; -} - -void CanvasItemMaterial::set_particles_anim_loop(bool p_loop) { - particles_anim_loop = p_loop; - RS::get_singleton()->material_set_param(_get_material(), shader_names->particles_anim_loop, particles_anim_loop); -} - -bool CanvasItemMaterial::get_particles_anim_loop() const { - return particles_anim_loop; -} - -void CanvasItemMaterial::_validate_property(PropertyInfo &property) const { - if (property.name.begins_with("particles_anim_") && !particles_animation) { - property.usage = PROPERTY_USAGE_NONE; - } -} - -RID CanvasItemMaterial::get_shader_rid() const { - ERR_FAIL_COND_V(!shader_map.has(current_key), RID()); - return shader_map[current_key].shader; -} - -Shader::Mode CanvasItemMaterial::get_shader_mode() const { - return Shader::MODE_CANVAS_ITEM; -} - -void CanvasItemMaterial::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_blend_mode", "blend_mode"), &CanvasItemMaterial::set_blend_mode); - ClassDB::bind_method(D_METHOD("get_blend_mode"), &CanvasItemMaterial::get_blend_mode); - - ClassDB::bind_method(D_METHOD("set_light_mode", "light_mode"), &CanvasItemMaterial::set_light_mode); - ClassDB::bind_method(D_METHOD("get_light_mode"), &CanvasItemMaterial::get_light_mode); - - ClassDB::bind_method(D_METHOD("set_particles_animation", "particles_anim"), &CanvasItemMaterial::set_particles_animation); - ClassDB::bind_method(D_METHOD("get_particles_animation"), &CanvasItemMaterial::get_particles_animation); - - ClassDB::bind_method(D_METHOD("set_particles_anim_h_frames", "frames"), &CanvasItemMaterial::set_particles_anim_h_frames); - ClassDB::bind_method(D_METHOD("get_particles_anim_h_frames"), &CanvasItemMaterial::get_particles_anim_h_frames); - - ClassDB::bind_method(D_METHOD("set_particles_anim_v_frames", "frames"), &CanvasItemMaterial::set_particles_anim_v_frames); - ClassDB::bind_method(D_METHOD("get_particles_anim_v_frames"), &CanvasItemMaterial::get_particles_anim_v_frames); - - ClassDB::bind_method(D_METHOD("set_particles_anim_loop", "loop"), &CanvasItemMaterial::set_particles_anim_loop); - ClassDB::bind_method(D_METHOD("get_particles_anim_loop"), &CanvasItemMaterial::get_particles_anim_loop); - - ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_mode", PROPERTY_HINT_ENUM, "Mix,Add,Subtract,Multiply,Premultiplied Alpha"), "set_blend_mode", "get_blend_mode"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "light_mode", PROPERTY_HINT_ENUM, "Normal,Unshaded,Light Only"), "set_light_mode", "get_light_mode"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "particles_animation"), "set_particles_animation", "get_particles_animation"); - - ADD_PROPERTY(PropertyInfo(Variant::INT, "particles_anim_h_frames", PROPERTY_HINT_RANGE, "1,128,1"), "set_particles_anim_h_frames", "get_particles_anim_h_frames"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "particles_anim_v_frames", PROPERTY_HINT_RANGE, "1,128,1"), "set_particles_anim_v_frames", "get_particles_anim_v_frames"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "particles_anim_loop"), "set_particles_anim_loop", "get_particles_anim_loop"); - - BIND_ENUM_CONSTANT(BLEND_MODE_MIX); - BIND_ENUM_CONSTANT(BLEND_MODE_ADD); - BIND_ENUM_CONSTANT(BLEND_MODE_SUB); - BIND_ENUM_CONSTANT(BLEND_MODE_MUL); - BIND_ENUM_CONSTANT(BLEND_MODE_PREMULT_ALPHA); - - BIND_ENUM_CONSTANT(LIGHT_MODE_NORMAL); - BIND_ENUM_CONSTANT(LIGHT_MODE_UNSHADED); - BIND_ENUM_CONSTANT(LIGHT_MODE_LIGHT_ONLY); -} - -CanvasItemMaterial::CanvasItemMaterial() : - element(this) { - set_particles_anim_h_frames(1); - set_particles_anim_v_frames(1); - set_particles_anim_loop(false); - - current_key.invalid_key = 1; - _queue_shader_change(); -} - -CanvasItemMaterial::~CanvasItemMaterial() { - MutexLock lock(material_mutex); - - if (shader_map.has(current_key)) { - shader_map[current_key].users--; - if (shader_map[current_key].users == 0) { - //deallocate shader, as it's no longer in use - RS::get_singleton()->free(shader_map[current_key].shader); - shader_map.erase(current_key); - } - - RS::get_singleton()->material_set_shader(_get_material(), RID()); - } -} - -/////////////////////////////////////////////////////////////////// #ifdef TOOLS_ENABLED bool CanvasItem::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { if (_edit_use_rect()) { @@ -426,9 +151,7 @@ void CanvasItem::_update_callback() { current_item_drawn = this; notification(NOTIFICATION_DRAW); emit_signal(SceneStringNames::get_singleton()->draw); - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_draw); - } + GDVIRTUAL_CALL(_draw); current_item_drawn = nullptr; drawing = false; } @@ -526,7 +249,7 @@ void CanvasItem::_enter_canvas() { get_viewport()->gui_reset_canvas_sort_index(); } - get_tree()->call_group_flags(SceneTree::GROUP_CALL_UNIQUE, group, "_top_level_raise_self"); + get_tree()->call_group_flags(SceneTree::GROUP_CALL_UNIQUE, group, SNAME("_top_level_raise_self")); } else { CanvasItem *parent = get_parent_item(); @@ -545,7 +268,7 @@ 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 = ""; + group = StringName(); } void CanvasItem::_notification(int p_what) { @@ -591,7 +314,7 @@ void CanvasItem::_notification(int p_what) { break; } - if (group != "") { + if (group != StringName()) { get_tree()->call_group_flags(SceneTree::GROUP_CALL_UNIQUE, group, "_top_level_raise_self"); } else { CanvasItem *p = get_parent_item(); @@ -651,7 +374,7 @@ void CanvasItem::update() { pending_update = true; - MessageQueue::get_singleton()->push_call(this, "_update_callback"); + MessageQueue::get_singleton()->push_call(this, SNAME("_update_callback")); } void CanvasItem::set_modulate(const Color &p_modulate) { @@ -848,6 +571,12 @@ void CanvasItem::draw_texture_rect_region(const Ref<Texture2D> &p_texture, const p_texture->draw_rect_region(canvas_item, p_rect, p_src_rect, p_modulate, p_transpose, p_clip_uv); } +void CanvasItem::draw_msdf_texture_rect_region(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate, double p_outline, double p_pixel_range) { + ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); + ERR_FAIL_COND(p_texture.is_null()); + RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(canvas_item, p_rect, p_texture->get_rid(), p_src_rect, p_modulate, p_outline, p_pixel_range); +} + void CanvasItem::draw_style_box(const Ref<StyleBox> &p_style_box, const Rect2 &p_rect) { ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); @@ -959,8 +688,7 @@ void CanvasItem::_notify_transform(CanvasItem *p_node) { } } - for (List<CanvasItem *>::Element *E = p_node->children_items.front(); E; E = E->next()) { - CanvasItem *ci = E->get(); + for (CanvasItem *ci : p_node->children_items) { if (ci->top_level) { continue; } @@ -1159,6 +887,7 @@ void CanvasItem::_bind_methods() { ClassDB::bind_method(D_METHOD("draw_texture", "texture", "position", "modulate"), &CanvasItem::draw_texture, DEFVAL(Color(1, 1, 1, 1))); ClassDB::bind_method(D_METHOD("draw_texture_rect", "texture", "rect", "tile", "modulate", "transpose"), &CanvasItem::draw_texture_rect, DEFVAL(Color(1, 1, 1, 1)), DEFVAL(false)); ClassDB::bind_method(D_METHOD("draw_texture_rect_region", "texture", "rect", "src_rect", "modulate", "transpose", "clip_uv"), &CanvasItem::draw_texture_rect_region, DEFVAL(Color(1, 1, 1, 1)), DEFVAL(false), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("draw_msdf_texture_rect_region", "texture", "rect", "src_rect", "modulate", "outline", "pixel_range"), &CanvasItem::draw_msdf_texture_rect_region, DEFVAL(Color(1, 1, 1, 1)), DEFVAL(0.0), DEFVAL(4.0)); ClassDB::bind_method(D_METHOD("draw_style_box", "style_box", "rect"), &CanvasItem::draw_style_box); 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>())); @@ -1210,7 +939,7 @@ void CanvasItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_clip_children", "enable"), &CanvasItem::set_clip_children); ClassDB::bind_method(D_METHOD("is_clipping_children"), &CanvasItem::is_clipping_children); - BIND_VMETHOD(MethodInfo("_draw")); + GDVIRTUAL_BIND(_draw); ADD_GROUP("Visibility", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible"); @@ -1227,7 +956,7 @@ void CanvasItem::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_repeat", PROPERTY_HINT_ENUM, "Inherit,Disabled,Enabled,Mirror"), "set_texture_repeat", "get_texture_repeat"); ADD_GROUP("Material", ""); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,CanvasItemMaterial"), "set_material", "get_material"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "CanvasItemMaterial,ShaderMaterial"), "set_material", "get_material"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_parent_material"), "set_use_parent_material", "get_use_parent_material"); // ADD_PROPERTY(PropertyInfo(Variant::BOOL,"transform/notify"),"set_transform_notify","is_transform_notify_enabled"); @@ -1337,9 +1066,9 @@ void CanvasItem::_update_texture_filter_changed(bool p_propagate) { update(); if (p_propagate) { - for (List<CanvasItem *>::Element *E = children_items.front(); E; E = E->next()) { - if (!E->get()->top_level && E->get()->texture_filter == TEXTURE_FILTER_PARENT_NODE) { - E->get()->_update_texture_filter_changed(true); + for (CanvasItem *E : children_items) { + if (!E->top_level && E->texture_filter == TEXTURE_FILTER_PARENT_NODE) { + E->_update_texture_filter_changed(true); } } } @@ -1377,9 +1106,9 @@ void CanvasItem::_update_texture_repeat_changed(bool p_propagate) { RS::get_singleton()->canvas_item_set_default_texture_repeat(get_canvas_item(), texture_repeat_cache); update(); if (p_propagate) { - for (List<CanvasItem *>::Element *E = children_items.front(); E; E = E->next()) { - if (!E->get()->top_level && E->get()->texture_repeat == TEXTURE_REPEAT_PARENT_NODE) { - E->get()->_update_texture_repeat_changed(true); + for (CanvasItem *E : children_items) { + if (!E->top_level && E->texture_repeat == TEXTURE_REPEAT_PARENT_NODE) { + E->_update_texture_repeat_changed(true); } } } diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h index afdd18d76b..01ed47d4dc 100644 --- a/scene/main/canvas_item.h +++ b/scene/main/canvas_item.h @@ -33,132 +33,15 @@ #include "scene/main/node.h" #include "scene/main/scene_tree.h" -#include "scene/resources/material.h" -#include "scene/resources/multimesh.h" -#include "scene/resources/shader.h" -#include "scene/resources/texture.h" +#include "scene/resources/canvas_item_material.h" #include "servers/text_server.h" class CanvasLayer; -class Viewport; class Font; - +class MultiMesh; class StyleBox; - -class CanvasItemMaterial : public Material { - GDCLASS(CanvasItemMaterial, Material); - -public: - enum BlendMode { - BLEND_MODE_MIX, - BLEND_MODE_ADD, - BLEND_MODE_SUB, - BLEND_MODE_MUL, - BLEND_MODE_PREMULT_ALPHA, - BLEND_MODE_DISABLED - }; - - enum LightMode { - LIGHT_MODE_NORMAL, - LIGHT_MODE_UNSHADED, - LIGHT_MODE_LIGHT_ONLY - }; - -private: - union MaterialKey { - struct { - uint32_t blend_mode : 4; - uint32_t light_mode : 4; - uint32_t particles_animation : 1; - uint32_t invalid_key : 1; - }; - - uint32_t key = 0; - - bool operator<(const MaterialKey &p_key) const { - return key < p_key.key; - } - }; - - struct ShaderNames { - StringName particles_anim_h_frames; - StringName particles_anim_v_frames; - StringName particles_anim_loop; - }; - - static ShaderNames *shader_names; - - struct ShaderData { - RID shader; - int users = 0; - }; - - static Map<MaterialKey, ShaderData> shader_map; - - MaterialKey current_key; - - _FORCE_INLINE_ MaterialKey _compute_key() const { - MaterialKey mk; - mk.key = 0; - mk.blend_mode = blend_mode; - mk.light_mode = light_mode; - mk.particles_animation = particles_animation; - return mk; - } - - static Mutex material_mutex; - static SelfList<CanvasItemMaterial>::List *dirty_materials; - SelfList<CanvasItemMaterial> element; - - void _update_shader(); - _FORCE_INLINE_ void _queue_shader_change(); - _FORCE_INLINE_ bool _is_shader_dirty() const; - - BlendMode blend_mode = BLEND_MODE_MIX; - 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; - -protected: - static void _bind_methods(); - void _validate_property(PropertyInfo &property) const override; - -public: - void set_blend_mode(BlendMode p_blend_mode); - BlendMode get_blend_mode() const; - - void set_light_mode(LightMode p_light_mode); - LightMode get_light_mode() const; - - void set_particles_animation(bool p_particles_anim); - bool get_particles_animation() const; - - void set_particles_anim_h_frames(int p_frames); - int get_particles_anim_h_frames() const; - void set_particles_anim_v_frames(int p_frames); - int get_particles_anim_v_frames() const; - - void set_particles_anim_loop(bool p_loop); - bool get_particles_anim_loop() const; - - static void init_shaders(); - static void finish_shaders(); - static void flush_changes(); - - virtual RID get_shader_rid() const override; - - virtual Shader::Mode get_shader_mode() const override; - - CanvasItemMaterial(); - virtual ~CanvasItemMaterial(); -}; - -VARIANT_ENUM_CAST(CanvasItemMaterial::BlendMode) -VARIANT_ENUM_CAST(CanvasItemMaterial::LightMode) +class Window; +class World2D; class CanvasItem : public Node { GDCLASS(CanvasItem, Node); @@ -187,7 +70,7 @@ private: mutable SelfList<Node> xform_change; RID canvas_item; - String group; + StringName group; CanvasLayer *canvas_layer = nullptr; @@ -259,6 +142,7 @@ protected: void _notification(int p_what); static void _bind_methods(); + GDVIRTUAL0(_draw) public: enum { NOTIFICATION_TRANSFORM_CHANGED = SceneTree::NOTIFICATION_TRANSFORM_CHANGED, //unique @@ -342,6 +226,7 @@ public: void draw_texture(const Ref<Texture2D> &p_texture, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1, 1)); void draw_texture_rect(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false); void draw_texture_rect_region(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, bool p_clip_uv = false); + void draw_msdf_texture_rect_region(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), double p_outline = 0.0, double p_pixel_range = 4.0); void draw_style_box(const Ref<StyleBox> &p_style_box, const Rect2 &p_rect); void draw_primitive(const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs, Ref<Texture2D> p_texture = Ref<Texture2D>(), real_t p_width = 1); void draw_polygon(const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs = Vector<Point2>(), Ref<Texture2D> p_texture = Ref<Texture2D>()); diff --git a/scene/main/canvas_layer.h b/scene/main/canvas_layer.h index 5de1ebf18d..9d8e0c203d 100644 --- a/scene/main/canvas_layer.h +++ b/scene/main/canvas_layer.h @@ -32,7 +32,6 @@ #define CANVAS_LAYER_H #include "scene/main/node.h" -#include "scene/resources/world_2d.h" class Viewport; class CanvasLayer : public Node { diff --git a/scene/main/http_request.cpp b/scene/main/http_request.cpp index 775dfa4c46..f24d880045 100644 --- a/scene/main/http_request.cpp +++ b/scene/main/http_request.cpp @@ -30,7 +30,7 @@ #include "http_request.h" #include "core/io/compression.h" -#include "core/string/ustring.h" +#include "scene/main/timer.h" void HTTPRequest::_redirect_request(const String &p_new_url) { } @@ -151,7 +151,7 @@ Error HTTPRequest::request_raw(const String &p_url, const Vector<String> &p_cust client->set_blocking_mode(false); err = _request(); if (err != OK) { - call_deferred("_request_done", RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray()); + call_deferred(SNAME("_request_done"), RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray()); return ERR_CANT_CONNECT; } @@ -167,7 +167,7 @@ void HTTPRequest::_thread_func(void *p_userdata) { Error err = hr->_request(); if (err != OK) { - hr->call_deferred("_request_done", RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray()); + hr->call_deferred(SNAME("_request_done"), RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray()); } else { while (!hr->thread_request_quit.is_set()) { bool exit = hr->_update_connection(); @@ -209,7 +209,7 @@ void HTTPRequest::cancel_request() { bool HTTPRequest::_handle_response(bool *ret_value) { if (!client->has_response()) { - call_deferred("_request_done", RESULT_NO_RESPONSE, 0, PackedStringArray(), PackedByteArray()); + call_deferred(SNAME("_request_done"), RESULT_NO_RESPONSE, 0, PackedStringArray(), PackedByteArray()); *ret_value = true; return true; } @@ -220,24 +220,24 @@ bool HTTPRequest::_handle_response(bool *ret_value) { client->get_response_headers(&rheaders); response_headers.resize(0); downloaded.set(0); - for (List<String>::Element *E = rheaders.front(); E; E = E->next()) { - response_headers.push_back(E->get()); + for (const String &E : rheaders) { + response_headers.push_back(E); } if (response_code == 301 || response_code == 302) { // Handle redirect if (max_redirects >= 0 && redirections >= max_redirects) { - call_deferred("_request_done", RESULT_REDIRECT_LIMIT_REACHED, response_code, response_headers, PackedByteArray()); + call_deferred(SNAME("_request_done"), RESULT_REDIRECT_LIMIT_REACHED, response_code, response_headers, PackedByteArray()); *ret_value = true; return true; } String new_request; - for (List<String>::Element *E = rheaders.front(); E; E = E->next()) { - if (E->get().findn("Location: ") != -1) { - new_request = E->get().substr(9, E->get().length()).strip_edges(); + for (const String &E : rheaders) { + if (E.findn("Location: ") != -1) { + new_request = E.substr(9, E.length()).strip_edges(); } } @@ -273,7 +273,7 @@ bool HTTPRequest::_handle_response(bool *ret_value) { bool HTTPRequest::_update_connection() { switch (client->get_status()) { case HTTPClient::STATUS_DISCONNECTED: { - call_deferred("_request_done", RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray()); + call_deferred(SNAME("_request_done"), RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray()); return true; // End it, since it's doing something } break; case HTTPClient::STATUS_RESOLVING: { @@ -282,7 +282,7 @@ bool HTTPRequest::_update_connection() { return false; } break; case HTTPClient::STATUS_CANT_RESOLVE: { - call_deferred("_request_done", RESULT_CANT_RESOLVE, 0, PackedStringArray(), PackedByteArray()); + call_deferred(SNAME("_request_done"), RESULT_CANT_RESOLVE, 0, PackedStringArray(), PackedByteArray()); return true; } break; @@ -292,7 +292,7 @@ bool HTTPRequest::_update_connection() { return false; } break; // Connecting to IP case HTTPClient::STATUS_CANT_CONNECT: { - call_deferred("_request_done", RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray()); + call_deferred(SNAME("_request_done"), RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray()); return true; } break; @@ -307,16 +307,16 @@ bool HTTPRequest::_update_connection() { return ret_value; } - call_deferred("_request_done", RESULT_SUCCESS, response_code, response_headers, PackedByteArray()); + call_deferred(SNAME("_request_done"), RESULT_SUCCESS, response_code, response_headers, PackedByteArray()); return true; } if (body_len < 0) { // Chunked transfer is done - call_deferred("_request_done", RESULT_SUCCESS, response_code, response_headers, body); + call_deferred(SNAME("_request_done"), RESULT_SUCCESS, response_code, response_headers, body); return true; } - call_deferred("_request_done", RESULT_CHUNKED_BODY_SIZE_MISMATCH, response_code, response_headers, PackedByteArray()); + call_deferred(SNAME("_request_done"), RESULT_CHUNKED_BODY_SIZE_MISMATCH, response_code, response_headers, PackedByteArray()); return true; // Request might have been done } else { @@ -325,7 +325,7 @@ bool HTTPRequest::_update_connection() { int size = request_data.size(); Error err = client->request(method, request_string, headers, size > 0 ? request_data.ptr() : nullptr, size); if (err != OK) { - call_deferred("_request_done", RESULT_CONNECTION_ERROR, 0, PackedStringArray(), PackedByteArray()); + call_deferred(SNAME("_request_done"), RESULT_CONNECTION_ERROR, 0, PackedStringArray(), PackedByteArray()); return true; } @@ -348,7 +348,7 @@ bool HTTPRequest::_update_connection() { } if (!client->is_response_chunked() && client->get_response_body_length() == 0) { - call_deferred("_request_done", RESULT_SUCCESS, response_code, response_headers, PackedByteArray()); + call_deferred(SNAME("_request_done"), RESULT_SUCCESS, response_code, response_headers, PackedByteArray()); return true; } @@ -357,14 +357,14 @@ bool HTTPRequest::_update_connection() { body_len = client->get_response_body_length(); if (body_size_limit >= 0 && body_len > body_size_limit) { - call_deferred("_request_done", RESULT_BODY_SIZE_LIMIT_EXCEEDED, response_code, response_headers, PackedByteArray()); + call_deferred(SNAME("_request_done"), RESULT_BODY_SIZE_LIMIT_EXCEEDED, response_code, response_headers, PackedByteArray()); return true; } if (download_to_file != String()) { file = FileAccess::open(download_to_file, FileAccess::WRITE); if (!file) { - call_deferred("_request_done", RESULT_DOWNLOAD_FILE_CANT_OPEN, response_code, response_headers, PackedByteArray()); + call_deferred(SNAME("_request_done"), RESULT_DOWNLOAD_FILE_CANT_OPEN, response_code, response_headers, PackedByteArray()); return true; } } @@ -383,7 +383,7 @@ bool HTTPRequest::_update_connection() { const uint8_t *r = chunk.ptr(); file->store_buffer(r, chunk.size()); if (file->get_error() != OK) { - call_deferred("_request_done", RESULT_DOWNLOAD_FILE_WRITE_ERROR, response_code, response_headers, PackedByteArray()); + call_deferred(SNAME("_request_done"), RESULT_DOWNLOAD_FILE_WRITE_ERROR, response_code, response_headers, PackedByteArray()); return true; } } else { @@ -392,18 +392,18 @@ bool HTTPRequest::_update_connection() { } if (body_size_limit >= 0 && downloaded.get() > body_size_limit) { - call_deferred("_request_done", RESULT_BODY_SIZE_LIMIT_EXCEEDED, response_code, response_headers, PackedByteArray()); + call_deferred(SNAME("_request_done"), RESULT_BODY_SIZE_LIMIT_EXCEEDED, response_code, response_headers, PackedByteArray()); return true; } if (body_len >= 0) { if (downloaded.get() == body_len) { - call_deferred("_request_done", RESULT_SUCCESS, response_code, response_headers, body); + call_deferred(SNAME("_request_done"), RESULT_SUCCESS, response_code, response_headers, body); return true; } } else if (client->get_status() == HTTPClient::STATUS_DISCONNECTED) { // We read till EOF, with no errors. Request is done. - call_deferred("_request_done", RESULT_SUCCESS, response_code, response_headers, body); + call_deferred(SNAME("_request_done"), RESULT_SUCCESS, response_code, response_headers, body); return true; } @@ -411,11 +411,11 @@ bool HTTPRequest::_update_connection() { } break; // Request resulted in body: break which must be read case HTTPClient::STATUS_CONNECTION_ERROR: { - call_deferred("_request_done", RESULT_CONNECTION_ERROR, 0, PackedStringArray(), PackedByteArray()); + call_deferred(SNAME("_request_done"), RESULT_CONNECTION_ERROR, 0, PackedStringArray(), PackedByteArray()); return true; } break; case HTTPClient::STATUS_SSL_HANDSHAKE_ERROR: { - call_deferred("_request_done", RESULT_SSL_HANDSHAKE_ERROR, 0, PackedStringArray(), PackedByteArray()); + call_deferred(SNAME("_request_done"), RESULT_SSL_HANDSHAKE_ERROR, 0, PackedStringArray(), PackedByteArray()); return true; } break; } @@ -463,7 +463,7 @@ void HTTPRequest::_request_done(int p_status, int p_code, const PackedStringArra data = &p_data; } - emit_signal("request_completed", p_status, p_code, p_headers, *data); + emit_signal(SNAME("request_completed"), p_status, p_code, p_headers, *data); } void HTTPRequest::_notification(int p_what) { @@ -563,7 +563,7 @@ int HTTPRequest::get_timeout() { void HTTPRequest::_timeout() { cancel_request(); - call_deferred("_request_done", RESULT_TIMEOUT, 0, PackedStringArray(), PackedByteArray()); + call_deferred(SNAME("_request_done"), RESULT_TIMEOUT, 0, PackedStringArray(), PackedByteArray()); } void HTTPRequest::_bind_methods() { diff --git a/scene/main/http_request.h b/scene/main/http_request.h index 22e822253f..673cf3a740 100644 --- a/scene/main/http_request.h +++ b/scene/main/http_request.h @@ -31,12 +31,12 @@ #ifndef HTTPREQUEST_H #define HTTPREQUEST_H -#include "core/io/file_access.h" #include "core/io/http_client.h" #include "core/os/thread.h" #include "core/templates/safe_refcount.h" -#include "node.h" -#include "scene/main/timer.h" +#include "scene/main/node.h" + +class Timer; class HTTPRequest : public Node { GDCLASS(HTTPRequest, Node); diff --git a/scene/main/instance_placeholder.cpp b/scene/main/instance_placeholder.cpp index 89dac5f5a8..b5ba1899ec 100644 --- a/scene/main/instance_placeholder.cpp +++ b/scene/main/instance_placeholder.cpp @@ -42,9 +42,9 @@ bool InstancePlaceholder::_set(const StringName &p_name, const Variant &p_value) } bool InstancePlaceholder::_get(const StringName &p_name, Variant &r_ret) const { - for (const List<PropSet>::Element *E = stored_values.front(); E; E = E->next()) { - if (E->get().name == p_name) { - r_ret = E->get().value; + for (const PropSet &E : stored_values) { + if (E.name == p_name) { + r_ret = E.value; return true; } } @@ -52,10 +52,10 @@ bool InstancePlaceholder::_get(const StringName &p_name, Variant &r_ret) const { } void InstancePlaceholder::_get_property_list(List<PropertyInfo> *p_list) const { - for (const List<PropSet>::Element *E = stored_values.front(); E; E = E->next()) { + for (const PropSet &E : stored_values) { PropertyInfo pi; - pi.name = E->get().name; - pi.type = E->get().value.get_type(); + pi.name = E.name; + pi.type = E.value.get_type(); pi.usage = PROPERTY_USAGE_STORAGE; p_list->push_back(pi); @@ -95,8 +95,8 @@ Node *InstancePlaceholder::create_instance(bool p_replace, const Ref<PackedScene scene->set_name(get_name()); int pos = get_index(); - for (List<PropSet>::Element *E = stored_values.front(); E; E = E->next()) { - scene->set(E->get().name, E->get().value); + for (const PropSet &E : stored_values) { + scene->set(E.name, E.value); } if (p_replace) { @@ -114,10 +114,10 @@ Dictionary InstancePlaceholder::get_stored_values(bool p_with_order) { Dictionary ret; PackedStringArray order; - for (List<PropSet>::Element *E = stored_values.front(); E; E = E->next()) { - ret[E->get().name] = E->get().value; + for (const PropSet &E : stored_values) { + ret[E.name] = E.value; if (p_with_order) { - order.push_back(E->get().name); + order.push_back(E.name); } }; diff --git a/scene/main/node.cpp b/scene/main/node.cpp index ac092d9862..05409b7bfe 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -48,22 +48,18 @@ #include <stdint.h> VARIANT_ENUM_CAST(Node::ProcessMode); +VARIANT_ENUM_CAST(Node::InternalMode); int Node::orphan_node_count = 0; void Node::_notification(int p_notification) { switch (p_notification) { case NOTIFICATION_PROCESS: { - if (get_script_instance()) { - Variant time = get_process_delta_time(); - get_script_instance()->call(SceneStringNames::get_singleton()->_process, time); - } + GDVIRTUAL_CALL(_process, get_process_delta_time()); + } break; case NOTIFICATION_PHYSICS_PROCESS: { - if (get_script_instance()) { - Variant time = get_physics_process_delta_time(); - get_script_instance()->call(SceneStringNames::get_singleton()->_physics_process, time); - } + GDVIRTUAL_CALL(_physics_process, get_physics_process_delta_time()); } break; case NOTIFICATION_ENTER_TREE: { @@ -116,6 +112,9 @@ void Node::_notification(int p_notification) { memdelete(data.path_cache); data.path_cache = nullptr; } + if (data.filename.length()) { + get_multiplayer()->scene_enter_exit_notify(data.filename, this, false); + } } break; case NOTIFICATION_PATH_CHANGED: { if (data.path_cache) { @@ -124,28 +123,30 @@ void Node::_notification(int p_notification) { } } break; case NOTIFICATION_READY: { - if (get_script_instance()) { - if (get_script_instance()->has_method(SceneStringNames::get_singleton()->_input)) { - set_process_input(true); - } + if (GDVIRTUAL_IS_OVERRIDDEN(_input)) { + set_process_input(true); + } - if (get_script_instance()->has_method(SceneStringNames::get_singleton()->_unhandled_input)) { - set_process_unhandled_input(true); - } + if (GDVIRTUAL_IS_OVERRIDDEN(_unhandled_input)) { + set_process_unhandled_input(true); + } - if (get_script_instance()->has_method(SceneStringNames::get_singleton()->_unhandled_key_input)) { - set_process_unhandled_key_input(true); - } + if (GDVIRTUAL_IS_OVERRIDDEN(_unhandled_key_input)) { + set_process_unhandled_key_input(true); + } - if (get_script_instance()->has_method(SceneStringNames::get_singleton()->_process)) { - set_process(true); - } + if (GDVIRTUAL_IS_OVERRIDDEN(_process)) { + set_process(true); + } + if (GDVIRTUAL_IS_OVERRIDDEN(_physics_process)) { + set_physics_process(true); + } - if (get_script_instance()->has_method(SceneStringNames::get_singleton()->_physics_process)) { - set_physics_process(true); - } + GDVIRTUAL_CALL(_ready); - get_script_instance()->call(SceneStringNames::get_singleton()->_ready); + if (data.filename.length()) { + ERR_FAIL_COND(!is_inside_tree()); + get_multiplayer()->scene_enter_exit_notify(data.filename, this, true); } } break; @@ -214,9 +215,7 @@ void Node::_propagate_enter_tree() { notification(NOTIFICATION_ENTER_TREE); - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_enter_tree); - } + GDVIRTUAL_CALL(_enter_tree); emit_signal(SceneStringNames::get_singleton()->tree_entered); @@ -262,9 +261,8 @@ void Node::_propagate_exit_tree() { data.blocked--; - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_exit_tree); - } + GDVIRTUAL_CALL(_exit_tree); + emit_signal(SceneStringNames::get_singleton()->tree_exiting); notification(NOTIFICATION_EXIT_TREE, true); @@ -293,14 +291,40 @@ void Node::_propagate_exit_tree() { void Node::move_child(Node *p_child, int p_pos) { ERR_FAIL_NULL(p_child); - ERR_FAIL_INDEX_MSG(p_pos, data.children.size() + 1, vformat("Invalid new child position: %d.", p_pos)); ERR_FAIL_COND_MSG(p_child->data.parent != this, "Child is not a child of this node."); + + // We need to check whether node is internal and move it only in the relevant node range. + if (p_child->_is_internal_front()) { + ERR_FAIL_INDEX_MSG(p_pos, data.internal_children_front, vformat("Invalid new child position: %d. Child is internal.", p_pos)); + _move_child(p_child, p_pos); + } else if (p_child->_is_internal_back()) { + ERR_FAIL_INDEX_MSG(p_pos, data.internal_children_back, vformat("Invalid new child position: %d. Child is internal.", p_pos)); + _move_child(p_child, data.children.size() - data.internal_children_back + p_pos); + } else { + ERR_FAIL_INDEX_MSG(p_pos, data.children.size() + 1 - data.internal_children_front - data.internal_children_back, vformat("Invalid new child position: %d.", p_pos)); + _move_child(p_child, p_pos + data.internal_children_front); + } +} + +void Node::_move_child(Node *p_child, int p_pos, bool p_ignore_end) { ERR_FAIL_COND_MSG(data.blocked > 0, "Parent node is busy setting up children, move_child() failed. Consider using call_deferred(\"move_child\") instead (or \"popup\" if this is from a popup)."); // Specifying one place beyond the end // means the same as moving to the last position - if (p_pos == data.children.size()) { - p_pos--; + if (!p_ignore_end) { // p_ignore_end is a little hack to make back internal children work properly. + if (p_child->_is_internal_front()) { + if (p_pos == data.internal_children_front) { + p_pos--; + } + } else if (p_child->_is_internal_back()) { + if (p_pos == data.children.size()) { + p_pos--; + } + } else { + if (p_pos == data.children.size() - data.internal_children_back) { + p_pos--; + } + } } if (p_child->data.pos == p_pos) { @@ -341,7 +365,14 @@ void Node::raise() { return; } - data.parent->move_child(this, data.parent->data.children.size() - 1); + // Internal children move within a different index range. + if (_is_internal_front()) { + data.parent->move_child(this, data.parent->data.internal_children_front - 1); + } else if (_is_internal_back()) { + data.parent->move_child(this, data.parent->data.internal_children_back - 1); + } else { + data.parent->move_child(this, data.parent->get_child_count(false) - 1); + } } void Node::add_child_notify(Node *p_child) { @@ -442,7 +473,7 @@ void Node::set_process_mode(ProcessMode p_mode) { // This is required for the editor to update the visibility of disabled nodes // It's very expensive during runtime to change, so editor-only if (Engine::get_singleton()->is_editor_hint()) { - get_tree()->emit_signal("tree_process_mode_changed"); + get_tree()->emit_signal(SNAME("tree_process_mode_changed")); } #endif } @@ -485,32 +516,32 @@ void Node::_propagate_process_owner(Node *p_owner, int p_pause_notification, int } } -void Node::set_network_master(int p_peer_id, bool p_recursive) { - data.network_master = p_peer_id; +void Node::set_multiplayer_authority(int p_peer_id, bool p_recursive) { + data.multiplayer_authority = p_peer_id; if (p_recursive) { for (int i = 0; i < data.children.size(); i++) { - data.children[i]->set_network_master(p_peer_id, true); + data.children[i]->set_multiplayer_authority(p_peer_id, true); } } } -int Node::get_network_master() const { - return data.network_master; +int Node::get_multiplayer_authority() const { + return data.multiplayer_authority; } -bool Node::is_network_master() const { +bool Node::is_multiplayer_authority() const { ERR_FAIL_COND_V(!is_inside_tree(), false); - return get_multiplayer()->get_network_unique_id() == data.network_master; + return get_multiplayer()->get_unique_id() == data.multiplayer_authority; } /***** RPC CONFIG ********/ -uint16_t Node::rpc_config(const StringName &p_method, MultiplayerAPI::RPCMode p_rpc_mode, NetworkedMultiplayerPeer::TransferMode p_transfer_mode, int p_channel) { +uint16_t Node::rpc_config(const StringName &p_method, Multiplayer::RPCMode p_rpc_mode, Multiplayer::TransferMode p_transfer_mode, int p_channel) { for (int i = 0; i < data.rpc_methods.size(); i++) { if (data.rpc_methods[i].name == p_method) { - MultiplayerAPI::RPCConfig &nd = data.rpc_methods.write[i]; + Multiplayer::RPCConfig &nd = data.rpc_methods.write[i]; nd.rpc_mode = p_rpc_mode; nd.transfer_mode = p_transfer_mode; nd.channel = p_channel; @@ -518,7 +549,7 @@ uint16_t Node::rpc_config(const StringName &p_method, MultiplayerAPI::RPCMode p_ } } // New method - MultiplayerAPI::RPCConfig nd; + Multiplayer::RPCConfig nd; nd.name = p_method; nd.rpc_mode = p_rpc_mode; nd.transfer_mode = p_transfer_mode; @@ -611,7 +642,7 @@ Variant Node::_rpc_id_bind(const Variant **p_args, int p_argcount, Callable::Cal void Node::rpcp(int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) { ERR_FAIL_COND(!is_inside_tree()); - get_multiplayer()->rpcp(this, p_peer_id, true, p_method, p_arg, p_argcount); + get_multiplayer()->rpcp(this, p_peer_id, p_method, p_arg, p_argcount); } Ref<MultiplayerAPI> Node::get_multiplayer() const { @@ -632,7 +663,7 @@ void Node::set_custom_multiplayer(Ref<MultiplayerAPI> p_multiplayer) { multiplayer = p_multiplayer; } -Vector<MultiplayerAPI::RPCConfig> Node::get_node_rpc_methods() const { +Vector<Multiplayer::RPCConfig> Node::get_node_rpc_methods() const { return data.rpc_methods; } @@ -705,7 +736,7 @@ bool Node::is_enabled() const { return _is_enabled(); } -float Node::get_physics_process_delta_time() const { +double Node::get_physics_process_delta_time() const { if (data.tree) { return data.tree->get_physics_process_time(); } else { @@ -713,7 +744,7 @@ float Node::get_physics_process_delta_time() const { } } -float Node::get_process_delta_time() const { +double Node::get_process_delta_time() const { if (data.tree) { return data.tree->get_process_time(); } else { @@ -868,7 +899,7 @@ void Node::set_name(const String &p_name) { propagate_notification(NOTIFICATION_PATH_CHANGED); if (is_inside_tree()) { - emit_signal("renamed"); + emit_signal(SNAME("renamed")); get_tree()->node_renamed(this); get_tree()->tree_changed(); } @@ -1060,6 +1091,10 @@ void Node::_add_child_nocheck(Node *p_child, const StringName &p_name) { p_child->data.pos = data.children.size(); data.children.push_back(p_child); p_child->data.parent = this; + + if (data.internal_children_back > 0) { + _move_child(p_child, data.children.size() - data.internal_children_back - 1); + } p_child->notification(NOTIFICATION_PARENTED); if (data.tree) { @@ -1072,7 +1107,7 @@ void Node::_add_child_nocheck(Node *p_child, const StringName &p_name) { add_child_notify(p_child); } -void Node::add_child(Node *p_child, bool p_legible_unique_name) { +void Node::add_child(Node *p_child, bool p_legible_unique_name, InternalMode p_internal) { ERR_FAIL_NULL(p_child); ERR_FAIL_COND_MSG(p_child == this, vformat("Can't add child '%s' to itself.", p_child->get_name())); // adding to itself! ERR_FAIL_COND_MSG(p_child->data.parent, vformat("Can't add child '%s' to '%s', already has a parent '%s'.", p_child->get_name(), get_name(), p_child->data.parent->get_name())); //Fail if node has a parent @@ -1081,19 +1116,35 @@ void Node::add_child(Node *p_child, bool p_legible_unique_name) { #endif ERR_FAIL_COND_MSG(data.blocked > 0, "Parent node is busy setting up children, add_node() failed. Consider using call_deferred(\"add_child\", child) instead."); - /* Validate name */ _validate_child_name(p_child, p_legible_unique_name); - _add_child_nocheck(p_child, p_child->data.name); + + if (p_internal == INTERNAL_MODE_FRONT) { + _move_child(p_child, data.internal_children_front); + data.internal_children_front++; + } else if (p_internal == INTERNAL_MODE_BACK) { + if (data.internal_children_back > 0) { + _move_child(p_child, data.children.size() - 1, true); + } + data.internal_children_back++; + } } void Node::add_sibling(Node *p_sibling, bool p_legible_unique_name) { ERR_FAIL_NULL(p_sibling); + ERR_FAIL_NULL(data.parent); ERR_FAIL_COND_MSG(p_sibling == this, vformat("Can't add sibling '%s' to itself.", p_sibling->get_name())); // adding to itself! ERR_FAIL_COND_MSG(data.blocked > 0, "Parent node is busy setting up children, add_sibling() failed. Consider using call_deferred(\"add_sibling\", sibling) instead."); - get_parent()->add_child(p_sibling, p_legible_unique_name); - get_parent()->move_child(p_sibling, this->get_index() + 1); + InternalMode internal = INTERNAL_MODE_DISABLED; + if (_is_internal_front()) { // The sibling will have the same internal status. + internal = INTERNAL_MODE_FRONT; + } else if (_is_internal_back()) { + internal = INTERNAL_MODE_BACK; + } + + data.parent->add_child(p_sibling, p_legible_unique_name, internal); + data.parent->_move_child(p_sibling, get_index() + 1); } void Node::_propagate_validate_owner() { @@ -1147,7 +1198,12 @@ void Node::remove_child(Node *p_child) { ERR_FAIL_COND_MSG(idx == -1, vformat("Cannot remove child node '%s' as it is not a child of this node.", p_child->get_name())); //ERR_FAIL_COND( p_child->data.blocked > 0 ); - //if (data.scene) { does not matter + // If internal child, update the counter. + if (p_child->_is_internal_front()) { + data.internal_children_front--; + } else if (p_child->_is_internal_back()) { + data.internal_children_back--; + } p_child->_set_tree(nullptr); //} @@ -1177,17 +1233,29 @@ void Node::remove_child(Node *p_child) { } } -int Node::get_child_count() const { - return data.children.size(); +int Node::get_child_count(bool p_include_internal) const { + if (p_include_internal) { + return data.children.size(); + } else { + return data.children.size() - data.internal_children_front - data.internal_children_back; + } } -Node *Node::get_child(int p_index) const { - if (p_index < 0) { - p_index += data.children.size(); +Node *Node::get_child(int p_index, bool p_include_internal) const { + if (p_include_internal) { + if (p_index < 0) { + p_index += data.children.size(); + } + ERR_FAIL_INDEX_V(p_index, data.children.size(), nullptr); + return data.children[p_index]; + } else { + if (p_index < 0) { + p_index += data.children.size() - data.internal_children_front - data.internal_children_back; + } + ERR_FAIL_INDEX_V(p_index, data.children.size() - data.internal_children_front - data.internal_children_back, nullptr); + p_index += data.internal_children_front; + return data.children[p_index]; } - ERR_FAIL_INDEX_V(p_index, data.children.size(), nullptr); - - return data.children[p_index]; } Node *Node::_get_child_by_name(const StringName &p_name) const { @@ -1597,8 +1665,8 @@ Array Node::_get_groups() const { Array groups; List<GroupInfo> gi; get_groups(&gi); - for (List<GroupInfo>::Element *E = gi.front(); E; E = E->next()) { - groups.push_back(E->get().name); + for (const GroupInfo &E : gi) { + groups.push_back(E.name); } return groups; @@ -1719,7 +1787,13 @@ void Node::_propagate_replace_owner(Node *p_owner, Node *p_by_owner) { data.blocked--; } -int Node::get_index() const { +int Node::get_index(bool p_include_internal) const { + // p_include_internal = false doesn't make sense if the node is internal. + ERR_FAIL_COND_V_MSG(!p_include_internal && (_is_internal_front() || _is_internal_back()), -1, "Node is internal. Can't get index with 'include_internal' being false."); + + if (data.parent && !p_include_internal) { + return data.pos - data.parent->data.internal_children_front; + } return data.pos; } @@ -1822,6 +1896,18 @@ Node *Node::get_deepest_editable_node(Node *p_start_node) const { return node; } +String Node::to_string() { + if (get_script_instance()) { + bool valid; + String ret = get_script_instance()->to_string(&valid); + if (valid) { + return ret; + } + } + + return (get_name() ? String(get_name()) + ":" : "") + Object::to_string(); +} + void Node::set_scene_instance_state(const Ref<SceneState> &p_state) { data.instance_state = p_state; } @@ -1935,18 +2021,18 @@ Node *Node::_duplicate(int p_flags, Map<const Node *, Node *> *r_duplimap) const List<PropertyInfo> plist; N->get()->get_property_list(&plist); - for (List<PropertyInfo>::Element *E = plist.front(); E; E = E->next()) { - if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) { + for (const PropertyInfo &E : plist) { + if (!(E.usage & PROPERTY_USAGE_STORAGE)) { continue; } - String name = E->get().name; + String name = E.name; if (name == script_property_name) { continue; } Variant value = N->get()->get(name).duplicate(true); - if (E->get().usage & PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE) { + if (E.usage & PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE) { Resource *res = Object::cast_to<Resource>(value); if (res) { // Duplicate only if it's a resource current_node->set(name, res->duplicate()); @@ -1971,14 +2057,14 @@ Node *Node::_duplicate(int p_flags, Map<const Node *, Node *> *r_duplimap) const if (p_flags & DUPLICATE_GROUPS) { List<GroupInfo> gi; get_groups(&gi); - for (List<GroupInfo>::Element *E = gi.front(); E; E = E->next()) { + for (const GroupInfo &E : gi) { #ifdef TOOLS_ENABLED - if ((p_flags & DUPLICATE_FROM_EDITOR) && !E->get().persistent) { + if ((p_flags & DUPLICATE_FROM_EDITOR) && !E.persistent) { continue; } #endif - node->add_to_group(E->get().name, E->get().persistent); + node->add_to_group(E.name, E.persistent); } } @@ -2002,21 +2088,21 @@ Node *Node::_duplicate(int p_flags, Map<const Node *, Node *> *r_duplimap) const } } - for (List<const Node *>::Element *E = hidden_roots.front(); E; E = E->next()) { - Node *parent = node->get_node(get_path_to(E->get()->data.parent)); + for (const Node *&E : hidden_roots) { + Node *parent = node->get_node(get_path_to(E->data.parent)); if (!parent) { memdelete(node); return nullptr; } - Node *dup = E->get()->_duplicate(p_flags, r_duplimap); + Node *dup = E->_duplicate(p_flags, r_duplimap); if (!dup) { memdelete(node); return nullptr; } parent->add_child(dup); - int pos = E->get()->get_index(); + int pos = E->get_index(); if (pos < parent->get_child_count() - 1) { parent->move_child(dup, pos); @@ -2061,17 +2147,17 @@ void Node::remap_node_resources(Node *p_node, const Map<RES, RES> &p_resource_re List<PropertyInfo> props; p_node->get_property_list(&props); - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) { + for (const PropertyInfo &E : props) { + if (!(E.usage & PROPERTY_USAGE_STORAGE)) { continue; } - Variant v = p_node->get(E->get().name); + Variant v = p_node->get(E.name); if (v.is_ref()) { RES res = v; if (res.is_valid()) { if (p_resource_remap.has(res)) { - p_node->set(E->get().name, p_resource_remap[res]); + p_node->set(E.name, p_resource_remap[res]); remap_nested_resources(res, p_resource_remap); } } @@ -2087,17 +2173,17 @@ void Node::remap_nested_resources(RES p_resource, const Map<RES, RES> &p_resourc List<PropertyInfo> props; p_resource->get_property_list(&props); - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) { + for (const PropertyInfo &E : props) { + if (!(E.usage & PROPERTY_USAGE_STORAGE)) { continue; } - Variant v = p_resource->get(E->get().name); + Variant v = p_resource->get(E.name); if (v.is_ref()) { RES res = v; if (res.is_valid()) { if (p_resource_remap.has(res)) { - p_resource->set(E->get().name, p_resource_remap[res]); + p_resource->set(E.name, p_resource_remap[res]); remap_nested_resources(res, p_resource_remap); } } @@ -2123,13 +2209,13 @@ void Node::_duplicate_signals(const Node *p_original, Node *p_copy) const { List<Connection> conns; n->get_all_signal_connections(&conns); - for (List<Connection>::Element *E = conns.front(); E; E = E->next()) { - if (E->get().flags & CONNECT_PERSIST) { + for (const Connection &E : conns) { + if (E.flags & CONNECT_PERSIST) { //user connected NodePath p = p_original->get_path_to(n); Node *copy = p_copy->get_node(p); - Node *target = Object::cast_to<Node>(E->get().callable.get_object()); + Node *target = Object::cast_to<Node>(E.callable.get_object()); if (!target) { continue; } @@ -2146,9 +2232,9 @@ void Node::_duplicate_signals(const Node *p_original, Node *p_copy) const { } if (copy && copytarget) { - const Callable copy_callable = Callable(copytarget, E->get().callable.get_method()); - if (!copy->is_connected(E->get().signal.get_name(), copy_callable)) { - copy->connect(E->get().signal.get_name(), copy_callable, E->get().binds, E->get().flags); + const Callable copy_callable = Callable(copytarget, E.callable.get_method()); + if (!copy->is_connected(E.signal.get_name(), copy_callable)) { + copy->connect(E.signal.get_name(), copy_callable, E.binds, E.flags); } } } @@ -2182,8 +2268,8 @@ void Node::replace_by(Node *p_node, bool p_keep_groups) { List<GroupInfo> groups; get_groups(&groups); - for (List<GroupInfo>::Element *E = groups.front(); E; E = E->next()) { - p_node->add_to_group(E->get().name, E->get().persistent); + for (const GroupInfo &E : groups) { + p_node->add_to_group(E.name, E.persistent); } } @@ -2229,9 +2315,7 @@ void Node::_replace_connections_target(Node *p_new_target) { List<Connection> cl; get_signals_connected_to_this(&cl); - for (List<Connection>::Element *E = cl.front(); E; E = E->next()) { - Connection &c = E->get(); - + for (const Connection &c : cl) { if (c.flags & CONNECT_PERSIST) { c.signal.get_object()->disconnect(c.signal.get_name(), Callable(this, c.callable.get_method())); bool valid = p_new_target->has_method(c.callable.get_method()) || Ref<Script>(p_new_target->get_script()).is_null() || Ref<Script>(p_new_target->get_script())->has_method(c.callable.get_method()); @@ -2421,12 +2505,12 @@ void Node::queue_delete() { } } -TypedArray<Node> Node::_get_children() const { +TypedArray<Node> Node::_get_children(bool p_include_internal) const { TypedArray<Node> arr; - int cc = get_child_count(); + int cc = get_child_count(p_include_internal); arr.resize(cc); for (int i = 0; i < cc; i++) { - arr[i] = get_child(i); + arr[i] = get_child(i, p_include_internal); } return arr; @@ -2448,7 +2532,7 @@ NodePath Node::get_import_path() const { static void _add_nodes_to_options(const Node *p_base, const Node *p_node, List<String> *r_options) { #ifdef TOOLS_ENABLED - const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", 0) ? "'" : "\""; + const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\""; #else const String quote_style = "\""; #endif @@ -2457,7 +2541,7 @@ static void _add_nodes_to_options(const Node *p_base, const Node *p_node, List<S return; } String n = p_base->get_path_to(p_node); - r_options->push_back(quote_style + n + quote_style); + r_options->push_back(n.quote(quote_style)); for (int i = 0; i < p_node->get_child_count(); i++) { _add_nodes_to_options(p_base, p_node->get_child(i), r_options); } @@ -2479,9 +2563,14 @@ void Node::clear_internal_tree_resource_paths() { } TypedArray<String> Node::get_configuration_warnings() const { - if (get_script_instance() && get_script_instance()->get_script().is_valid() && - get_script_instance()->get_script()->is_tool() && get_script_instance()->has_method("_get_configuration_warnings")) { - return get_script_instance()->call("_get_configuration_warnings"); + 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]; + } + return ret; } return Array(); } @@ -2493,7 +2582,9 @@ String Node::get_configuration_warnings_as_string() const { if (i > 0) { all_warnings += "\n\n"; } - all_warnings += String(warnings[i]); + // Format as a bullet point list to make multiple warnings easier to distinguish + // from each other. + all_warnings += String::utf8("• ") + String(warnings[i]); } return all_warnings; } @@ -2525,6 +2616,37 @@ void Node::request_ready() { data.ready_first = true; } +void Node::_call_input(const Ref<InputEvent> &p_event) { + GDVIRTUAL_CALL(_input, p_event); + if (!is_inside_tree() || !get_viewport() || get_viewport()->is_input_handled()) { + return; + } + input(p_event); +} +void Node::_call_unhandled_input(const Ref<InputEvent> &p_event) { + GDVIRTUAL_CALL(_unhandled_input, p_event); + if (!is_inside_tree() || !get_viewport() || get_viewport()->is_input_handled()) { + return; + } + unhandled_input(p_event); +} +void Node::_call_unhandled_key_input(const Ref<InputEvent> &p_event) { + GDVIRTUAL_CALL(_unhandled_key_input, p_event); + if (!is_inside_tree() || !get_viewport() || get_viewport()->is_input_handled()) { + return; + } + unhandled_key_input(p_event); +} + +void Node::input(const Ref<InputEvent> &p_event) { +} + +void Node::unhandled_input(const Ref<InputEvent> &p_event) { +} + +void Node::unhandled_key_input(const Ref<InputEvent> &p_key_event) { +} + void Node::_bind_methods() { GLOBAL_DEF("editor/node_naming/name_num_separator", 0); ProjectSettings::get_singleton()->set_custom_property_info("editor/node_naming/name_num_separator", PropertyInfo(Variant::INT, "editor/node_naming/name_num_separator", PROPERTY_HINT_ENUM, "None,Space,Underscore,Dash")); @@ -2535,11 +2657,11 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("set_name", "name"), &Node::set_name); ClassDB::bind_method(D_METHOD("get_name"), &Node::get_name); - ClassDB::bind_method(D_METHOD("add_child", "node", "legible_unique_name"), &Node::add_child, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("add_child", "node", "legible_unique_name", "internal"), &Node::add_child, DEFVAL(false), DEFVAL(0)); ClassDB::bind_method(D_METHOD("remove_child", "node"), &Node::remove_child); - ClassDB::bind_method(D_METHOD("get_child_count"), &Node::get_child_count); - ClassDB::bind_method(D_METHOD("get_children"), &Node::_get_children); - ClassDB::bind_method(D_METHOD("get_child", "idx"), &Node::get_child); + ClassDB::bind_method(D_METHOD("get_child_count", "include_internal"), &Node::get_child_count, DEFVAL(false)); // Note that the default value bound for include_internal is false, while the method is declared with true. This is because internal nodes are irrelevant for GDSCript. + ClassDB::bind_method(D_METHOD("get_children", "include_internal"), &Node::_get_children, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_child", "idx", "include_internal"), &Node::get_child, DEFVAL(false)); ClassDB::bind_method(D_METHOD("has_node", "path"), &Node::has_node); 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); @@ -2563,7 +2685,7 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("set_owner", "owner"), &Node::set_owner); ClassDB::bind_method(D_METHOD("get_owner"), &Node::get_owner); ClassDB::bind_method(D_METHOD("remove_and_skip"), &Node::remove_and_skip); - ClassDB::bind_method(D_METHOD("get_index"), &Node::get_index); + ClassDB::bind_method(D_METHOD("get_index", "include_internal"), &Node::get_index, DEFVAL(false)); ClassDB::bind_method(D_METHOD("print_tree"), &Node::print_tree); ClassDB::bind_method(D_METHOD("print_tree_pretty"), &Node::print_tree_pretty); ClassDB::bind_method(D_METHOD("set_filename", "filename"), &Node::set_filename); @@ -2606,6 +2728,8 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("set_scene_instance_load_placeholder", "load_placeholder"), &Node::set_scene_instance_load_placeholder); ClassDB::bind_method(D_METHOD("get_scene_instance_load_placeholder"), &Node::get_scene_instance_load_placeholder); + ClassDB::bind_method(D_METHOD("set_editable_instance", "node", "is_editable"), &Node::set_editable_instance); + ClassDB::bind_method(D_METHOD("is_editable_instance", "node"), &Node::is_editable_instance); ClassDB::bind_method(D_METHOD("get_viewport"), &Node::get_viewport); @@ -2613,15 +2737,15 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("request_ready"), &Node::request_ready); - ClassDB::bind_method(D_METHOD("set_network_master", "id", "recursive"), &Node::set_network_master, DEFVAL(true)); - ClassDB::bind_method(D_METHOD("get_network_master"), &Node::get_network_master); + ClassDB::bind_method(D_METHOD("set_multiplayer_authority", "id", "recursive"), &Node::set_multiplayer_authority, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("get_multiplayer_authority"), &Node::get_multiplayer_authority); - ClassDB::bind_method(D_METHOD("is_network_master"), &Node::is_network_master); + 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", "transfer_mode", "channel"), &Node::rpc_config, DEFVAL(NetworkedMultiplayerPeer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("rpc_config", "method", "rpc_mode", "transfer_mode", "channel"), &Node::rpc_config, DEFVAL(Multiplayer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); ClassDB::bind_method(D_METHOD("set_editor_description", "editor_description"), &Node::set_editor_description); ClassDB::bind_method(D_METHOD("get_editor_description"), &Node::get_editor_description); @@ -2699,6 +2823,10 @@ void Node::_bind_methods() { BIND_ENUM_CONSTANT(DUPLICATE_SCRIPTS); BIND_ENUM_CONSTANT(DUPLICATE_USE_INSTANCING); + BIND_ENUM_CONSTANT(INTERNAL_MODE_DISABLED); + BIND_ENUM_CONSTANT(INTERNAL_MODE_FRONT); + BIND_ENUM_CONSTANT(INTERNAL_MODE_BACK); + ADD_SIGNAL(MethodInfo("ready")); ADD_SIGNAL(MethodInfo("renamed")); ADD_SIGNAL(MethodInfo("tree_entered")); @@ -2718,15 +2846,15 @@ void Node::_bind_methods() { ADD_GROUP("Editor Description", "editor_"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "editor_description", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_INTERNAL), "set_editor_description", "get_editor_description"); - BIND_VMETHOD(MethodInfo("_process", PropertyInfo(Variant::FLOAT, "delta"))); - BIND_VMETHOD(MethodInfo("_physics_process", PropertyInfo(Variant::FLOAT, "delta"))); - BIND_VMETHOD(MethodInfo("_enter_tree")); - BIND_VMETHOD(MethodInfo("_exit_tree")); - BIND_VMETHOD(MethodInfo("_ready")); - BIND_VMETHOD(MethodInfo("_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"))); - BIND_VMETHOD(MethodInfo("_unhandled_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"))); - BIND_VMETHOD(MethodInfo("_unhandled_key_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEventKey"))); - BIND_VMETHOD(MethodInfo(PropertyInfo(Variant::ARRAY, "", PROPERTY_HINT_ARRAY_TYPE, "String"), "_get_configuration_warnings")); + GDVIRTUAL_BIND(_process, "delta"); + GDVIRTUAL_BIND(_physics_process, "delta"); + GDVIRTUAL_BIND(_enter_tree); + GDVIRTUAL_BIND(_exit_tree); + GDVIRTUAL_BIND(_ready); + GDVIRTUAL_BIND(_get_configuration_warnings); + GDVIRTUAL_BIND(_input, "event"); + GDVIRTUAL_BIND(_unhandled_input, "event"); + GDVIRTUAL_BIND(_unhandled_key_input, "event"); } String Node::_get_name_num_separator() { diff --git a/scene/main/node.h b/scene/main/node.h index fc5af43829..198501eeac 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -67,6 +67,18 @@ public: #endif }; + enum NameCasing { + NAME_CASING_PASCAL_CASE, + NAME_CASING_CAMEL_CASE, + NAME_CASING_SNAKE_CASE + }; + + enum InternalMode { + INTERNAL_MODE_DISABLED, + INTERNAL_MODE_FRONT, + INTERNAL_MODE_BACK, + }; + struct Comparator { bool operator()(const Node *p_a, const Node *p_b) const { return p_b->is_greater_than(p_a); } }; @@ -91,6 +103,8 @@ private: Node *parent = nullptr; Node *owner = nullptr; Vector<Node *> children; + int internal_children_front = 0; + int internal_children_back = 0; int pos = -1; int depth = -1; int blocked = 0; // Safeguard that throws an error when attempting to modify the tree in a harmful way while being traversed. @@ -113,8 +127,8 @@ private: ProcessMode process_mode = PROCESS_MODE_INHERIT; Node *process_owner = nullptr; - int network_master = 1; // Server by default. - Vector<MultiplayerAPI::RPCConfig> rpc_methods; + int multiplayer_authority = 1; // Server by default. + Vector<Multiplayer::RPCConfig> rpc_methods; // Variables used to properly sort the node when processing, ignored otherwise. // TODO: Should move all the stuff below to bits. @@ -140,12 +154,6 @@ private: } data; - enum NameCasing { - NAME_CASING_PASCAL_CASE, - NAME_CASING_CAMEL_CASE, - NAME_CASING_SNAKE_CASE - }; - Ref<MultiplayerAPI> multiplayer; void _print_tree_pretty(const String &prefix, const bool last); @@ -172,12 +180,15 @@ private: void _duplicate_signals(const Node *p_original, Node *p_copy) const; Node *_duplicate(int p_flags, Map<const Node *, Node *> *r_duplimap = nullptr) const; - TypedArray<Node> _get_children() const; + TypedArray<Node> _get_children(bool p_include_internal = true) const; Array _get_groups() const; Variant _rpc_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); Variant _rpc_id_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); + _FORCE_INLINE_ bool _is_internal_front() const { return data.parent && data.pos < data.parent->data.internal_children_front; } + _FORCE_INLINE_ bool _is_internal_back() const { return data.parent && data.pos >= data.parent->data.children.size() - data.parent->data.internal_children_back; } + friend class SceneTree; void _set_tree(SceneTree *p_tree); @@ -202,11 +213,33 @@ protected: static String _get_name_num_separator(); friend class SceneState; + friend class MultiplayerReplicator; void _add_child_nocheck(Node *p_child, const StringName &p_name); void _set_owner_nocheck(Node *p_owner); void _set_name_nocheck(const StringName &p_name); + //call from SceneTree + void _call_input(const Ref<InputEvent> &p_event); + void _call_unhandled_input(const Ref<InputEvent> &p_event); + void _call_unhandled_key_input(const Ref<InputEvent> &p_event); + +protected: + virtual void input(const Ref<InputEvent> &p_event); + virtual void unhandled_input(const Ref<InputEvent> &p_event); + virtual void unhandled_key_input(const Ref<InputEvent> &p_key_event); + + GDVIRTUAL1(_process, double) + GDVIRTUAL1(_physics_process, double) + GDVIRTUAL0(_enter_tree) + GDVIRTUAL0(_exit_tree) + GDVIRTUAL0(_ready) + GDVIRTUAL0RC(Vector<String>, _get_configuration_warnings) + + GDVIRTUAL1(_input, Ref<InputEvent>) + GDVIRTUAL1(_unhandled_input, Ref<InputEvent>) + GDVIRTUAL1(_unhandled_key_input, Ref<InputEvent>) + public: enum { // you can make your own, but don't use the same numbers as other notifications in other nodes @@ -262,12 +295,12 @@ public: StringName get_name() const; void set_name(const String &p_name); - void add_child(Node *p_child, bool p_legible_unique_name = false); + void add_child(Node *p_child, bool p_legible_unique_name = false, InternalMode p_internal = INTERNAL_MODE_DISABLED); void add_sibling(Node *p_sibling, bool p_legible_unique_name = false); void remove_child(Node *p_child); - int get_child_count() const; - Node *get_child(int p_index) const; + int get_child_count(bool p_include_internal = true) const; + Node *get_child(int p_index, bool p_include_internal = true) const; 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; @@ -305,6 +338,7 @@ public: int get_persistent_group_count() const; void move_child(Node *p_child, int p_pos); + void _move_child(Node *p_child, int p_pos, bool p_ignore_end = false); void raise(); void set_owner(Node *p_owner); @@ -312,7 +346,7 @@ public: void get_owned_by(Node *p_by, List<Node *> *p_owned); void remove_and_skip(); - int get_index() const; + int get_index(bool p_include_internal = true) const; Ref<Tween> create_tween(); @@ -329,6 +363,8 @@ public: bool is_editable_instance(const Node *p_node) const; Node *get_deepest_editable_node(Node *p_start_node) const; + virtual String to_string() override; + /* NOTIFICATIONS */ void propagate_notification(int p_notification); @@ -337,11 +373,11 @@ public: /* PROCESSING */ void set_physics_process(bool p_process); - float get_physics_process_delta_time() const; + double get_physics_process_delta_time() const; bool is_physics_processing() const; void set_process(bool p_process); - float get_process_delta_time() const; + double get_process_delta_time() const; bool is_processing() const; void set_physics_process_internal(bool p_process_internal); @@ -426,12 +462,12 @@ public: bool is_displayed_folded() const; /* NETWORK */ - void set_network_master(int p_peer_id, bool p_recursive = true); - int get_network_master() const; - bool is_network_master() const; + void set_multiplayer_authority(int p_peer_id, bool p_recursive = true); + int get_multiplayer_authority() const; + bool is_multiplayer_authority() const; - uint16_t rpc_config(const StringName &p_method, MultiplayerAPI::RPCMode p_rpc_mode, NetworkedMultiplayerPeer::TransferMode p_transfer_mode, int p_channel = 0); // config a local method for RPC - Vector<MultiplayerAPI::RPCConfig> get_node_rpc_methods() const; + uint16_t rpc_config(const StringName &p_method, Multiplayer::RPCMode p_rpc_mode, Multiplayer::TransferMode p_transfer_mode, int p_channel = 0); // config a local method for RPC + Vector<Multiplayer::RPCConfig> get_node_rpc_methods() const; void rpc(const StringName &p_method, VARIANT_ARG_LIST); // RPC, honors RPCMode, TransferMode, channel void rpc_id(int p_peer_id, const StringName &p_method, VARIANT_ARG_LIST); // RPC to specific peer(s), honors RPCMode, TransferMode, channel diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 4b52c4e99f..e1b1b356a9 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -66,11 +66,11 @@ void SceneTreeTimer::_bind_methods() { ADD_SIGNAL(MethodInfo("timeout")); } -void SceneTreeTimer::set_time_left(float p_time) { +void SceneTreeTimer::set_time_left(double p_time) { time_left = p_time; } -float SceneTreeTimer::get_time_left() const { +double SceneTreeTimer::get_time_left() const { return time_left; } @@ -82,12 +82,19 @@ bool SceneTreeTimer::is_process_always() { return process_always; } +void SceneTreeTimer::set_ignore_time_scale(bool p_ignore) { + ignore_time_scale = p_ignore; +} + +bool SceneTreeTimer::is_ignore_time_scale() { + return ignore_time_scale; +} + void SceneTreeTimer::release_connections() { List<Connection> connections; get_all_signal_connections(&connections); - for (List<Connection>::Element *E = connections.front(); E; E = E->next()) { - Connection const &connection = E->get(); + for (const Connection &connection : connections) { disconnect(connection.signal.get_name(), connection.callable); } } @@ -169,8 +176,8 @@ void SceneTree::_flush_ugc() { v[i] = E->get()[i]; } - static_assert(VARIANT_ARG_MAX == 5, "This code needs to be updated if VARIANT_ARG_MAX != 5"); - call_group_flags(GROUP_CALL_REALTIME, E->key().group, E->key().call, v[0], v[1], v[2], v[3], v[4]); + static_assert(VARIANT_ARG_MAX == 8, "This code needs to be updated if VARIANT_ARG_MAX != 8"); + call_group_flags(GROUP_CALL_REALTIME, E->key().group, E->key().call, v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7]); unique_group_calls.erase(E); } @@ -396,7 +403,7 @@ void SceneTree::initialize() { MainLoop::initialize(); } -bool SceneTree::physics_process(float p_time) { +bool SceneTree::physics_process(double p_time) { root_lock++; current_frame++; @@ -406,11 +413,11 @@ bool SceneTree::physics_process(float p_time) { MainLoop::physics_process(p_time); physics_process_time = p_time; - emit_signal("physics_frame"); + emit_signal(SNAME("physics_frame")); - _notify_group_pause("physics_process_internal", Node::NOTIFICATION_INTERNAL_PHYSICS_PROCESS); - call_group_flags(GROUP_CALL_REALTIME, "_viewports", "_process_picking"); - _notify_group_pause("physics_process", Node::NOTIFICATION_PHYSICS_PROCESS); + _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); _flush_ugc(); MessageQueue::get_singleton()->flush(); //small little hack @@ -425,7 +432,7 @@ bool SceneTree::physics_process(float p_time) { return _quit; } -bool SceneTree::process(float p_time) { +bool SceneTree::process(double p_time) { root_lock++; MainLoop::process(p_time); @@ -436,14 +443,14 @@ bool SceneTree::process(float p_time) { multiplayer->poll(); } - emit_signal("process_frame"); + emit_signal(SNAME("process_frame")); MessageQueue::get_singleton()->flush(); //small little hack flush_transform_notifications(); - _notify_group_pause("process_internal", Node::NOTIFICATION_INTERNAL_PROCESS); - _notify_group_pause("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 @@ -466,12 +473,17 @@ bool SceneTree::process(float p_time) { E = N; continue; } - float time_left = E->get()->get_time_left(); - time_left -= p_time; + + double time_left = E->get()->get_time_left(); + if (E->get()->is_ignore_time_scale()) { + time_left -= Engine::get_singleton()->get_process_step(); + } else { + time_left -= p_time; + } E->get()->set_time_left(time_left); if (time_left < 0) { - E->get()->emit_signal("timeout"); + E->get()->emit_signal(SNAME("timeout")); timers.erase(E); } if (E == L) { @@ -487,10 +499,10 @@ bool SceneTree::process(float p_time) { _call_idle_callbacks(); #ifdef TOOLS_ENABLED - +#ifndef _3D_DISABLED if (Engine::get_singleton()->is_editor_hint()) { //simple hack to reload fallback environment if it changed from editor - String env_path = ProjectSettings::get_singleton()->get("rendering/environment/defaults/default_environment"); + String env_path = ProjectSettings::get_singleton()->get(SNAME("rendering/environment/defaults/default_environment")); env_path = env_path.strip_edges(); //user may have added a space or two String cpath; Ref<Environment> fallback = get_root()->get_world_3d()->get_fallback_environment(); @@ -510,8 +522,8 @@ bool SceneTree::process(float p_time) { get_root()->get_world_3d()->set_fallback_environment(fallback); } } - -#endif +#endif // _3D_DISABLED +#endif // TOOLS_ENABLED return _quit; } @@ -532,7 +544,7 @@ void SceneTree::process_tweens(float p_delta, bool p_physics) { } if (!E->get()->step(p_delta)) { - E->get()->set_valid(false); + E->get()->clear(); tweens.erase(E); } if (E == L) { @@ -559,8 +571,8 @@ void SceneTree::finalize() { } // cleanup timers - for (List<Ref<SceneTreeTimer>>::Element *E = timers.front(); E; E = E->next()) { - E->get()->release_connections(); + for (Ref<SceneTreeTimer> &timer : timers) { + timer->release_connections(); } timers.clear(); } @@ -846,16 +858,7 @@ void SceneTree::_notify_group_pause(const StringName &p_group, int p_notificatio } } -/* -void SceneMainLoop::_update_listener_2d() { - if (listener_2d.is_valid()) { - SpatialSound2DServer::get_singleton()->listener_set_space( listener_2d, world_2d->get_sound_space() ); - } -} - -*/ - -void SceneTree::_call_input_pause(const StringName &p_group, const StringName &p_method, const Ref<InputEvent> &p_input, Viewport *p_viewport) { +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); if (!E) { return; @@ -874,9 +877,6 @@ void SceneTree::_call_input_pause(const StringName &p_group, const StringName &p int node_count = nodes_copy.size(); Node **nodes = nodes_copy.ptrw(); - Variant arg = p_input; - const Variant *v[1] = { &arg }; - call_lock++; for (int i = node_count - 1; i >= 0; i--) { @@ -893,14 +893,16 @@ void SceneTree::_call_input_pause(const StringName &p_group, const StringName &p continue; } - Callable::CallError err; - // Call both script and native method. - if (n->get_script_instance()) { - n->get_script_instance()->call(p_method, (const Variant **)v, 1, err); - } - MethodBind *method = ClassDB::get_method(n->get_class_name(), p_method); - if (method) { - method->call(n, (const Variant **)v, 1, err); + switch (p_call_type) { + case CALL_INPUT_TYPE_INPUT: + n->_call_input(p_input); + break; + case CALL_INPUT_TYPE_UNHANDLED_INPUT: + n->_call_unhandled_input(p_input); + break; + case CALL_INPUT_TYPE_UNHANDLED_KEY_INPUT: + n->_call_unhandled_key_input(p_input); + break; } } @@ -927,8 +929,8 @@ Variant SceneTree::_call_group_flags(const Variant **p_args, int p_argcount, Cal v[i] = *p_args[i + 3]; } - static_assert(VARIANT_ARG_MAX == 5, "This code needs to be updated if VARIANT_ARG_MAX != 5"); - call_group_flags(flags, group, method, v[0], v[1], v[2], v[3], v[4]); + static_assert(VARIANT_ARG_MAX == 8, "This code needs to be updated if VARIANT_ARG_MAX != 8"); + call_group_flags(flags, group, method, v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7]); return Variant(); } @@ -947,8 +949,8 @@ Variant SceneTree::_call_group(const Variant **p_args, int p_argcount, Callable: v[i] = *p_args[i + 2]; } - static_assert(VARIANT_ARG_MAX == 5, "This code needs to be updated if VARIANT_ARG_MAX != 5"); - call_group_flags(0, group, method, v[0], v[1], v[2], v[3], v[4]); + static_assert(VARIANT_ARG_MAX == 8, "This code needs to be updated if VARIANT_ARG_MAX != 8"); + call_group_flags(0, group, method, v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7]); return Variant(); } @@ -1097,7 +1099,7 @@ Error SceneTree::change_scene_to(const Ref<PackedScene> &p_scene) { ERR_FAIL_COND_V(!new_scene, ERR_CANT_CREATE); } - call_deferred("_change_scene", new_scene); + call_deferred(SNAME("_change_scene"), new_scene); return OK; } @@ -1112,7 +1114,7 @@ void SceneTree::add_current_scene(Node *p_current) { root->add_child(p_current); } -Ref<SceneTreeTimer> SceneTree::create_timer(float p_delay_sec, bool p_process_always) { +Ref<SceneTreeTimer> SceneTree::create_timer(double p_delay_sec, bool p_process_always) { Ref<SceneTreeTimer> stt; stt.instantiate(); stt->set_process_always(p_process_always); @@ -1134,34 +1136,14 @@ Array SceneTree::get_processed_tweens() { ret.resize(tweens.size()); int i = 0; - for (List<Ref<Tween>>::Element *E = tweens.front(); E; E = E->next()) { - ret[i] = E->get(); + for (const Ref<Tween> &tween : tweens) { + ret[i] = tween; i++; } return ret; } -void SceneTree::_network_peer_connected(int p_id) { - emit_signal("network_peer_connected", p_id); -} - -void SceneTree::_network_peer_disconnected(int p_id) { - emit_signal("network_peer_disconnected", p_id); -} - -void SceneTree::_connected_to_server() { - emit_signal("connected_to_server"); -} - -void SceneTree::_connection_failed() { - emit_signal("connection_failed"); -} - -void SceneTree::_server_disconnected() { - emit_signal("server_disconnected"); -} - Ref<MultiplayerAPI> SceneTree::get_multiplayer() const { return multiplayer; } @@ -1177,58 +1159,8 @@ bool SceneTree::is_multiplayer_poll_enabled() const { void SceneTree::set_multiplayer(Ref<MultiplayerAPI> p_multiplayer) { ERR_FAIL_COND(!p_multiplayer.is_valid()); - if (multiplayer.is_valid()) { - multiplayer->disconnect("network_peer_connected", callable_mp(this, &SceneTree::_network_peer_connected)); - multiplayer->disconnect("network_peer_disconnected", callable_mp(this, &SceneTree::_network_peer_disconnected)); - multiplayer->disconnect("connected_to_server", callable_mp(this, &SceneTree::_connected_to_server)); - multiplayer->disconnect("connection_failed", callable_mp(this, &SceneTree::_connection_failed)); - multiplayer->disconnect("server_disconnected", callable_mp(this, &SceneTree::_server_disconnected)); - } - multiplayer = p_multiplayer; multiplayer->set_root_node(root); - - multiplayer->connect("network_peer_connected", callable_mp(this, &SceneTree::_network_peer_connected)); - multiplayer->connect("network_peer_disconnected", callable_mp(this, &SceneTree::_network_peer_disconnected)); - multiplayer->connect("connected_to_server", callable_mp(this, &SceneTree::_connected_to_server)); - multiplayer->connect("connection_failed", callable_mp(this, &SceneTree::_connection_failed)); - multiplayer->connect("server_disconnected", callable_mp(this, &SceneTree::_server_disconnected)); -} - -void SceneTree::set_network_peer(const Ref<NetworkedMultiplayerPeer> &p_network_peer) { - multiplayer->set_network_peer(p_network_peer); -} - -Ref<NetworkedMultiplayerPeer> SceneTree::get_network_peer() const { - return multiplayer->get_network_peer(); -} - -bool SceneTree::is_network_server() const { - return multiplayer->is_network_server(); -} - -bool SceneTree::has_network_peer() const { - return multiplayer->has_network_peer(); -} - -int SceneTree::get_network_unique_id() const { - return multiplayer->get_network_unique_id(); -} - -Vector<int> SceneTree::get_network_connected_peers() const { - return multiplayer->get_network_connected_peers(); -} - -int SceneTree::get_rpc_sender_id() const { - return multiplayer->get_rpc_sender_id(); -} - -void SceneTree::set_refuse_new_network_connections(bool p_refuse) { - multiplayer->set_refuse_new_network_connections(p_refuse); -} - -bool SceneTree::is_refusing_new_network_connections() const { - return multiplayer->is_refusing_new_network_connections(); } void SceneTree::_bind_methods() { @@ -1297,24 +1229,12 @@ void SceneTree::_bind_methods() { ClassDB::bind_method(D_METHOD("get_multiplayer"), &SceneTree::get_multiplayer); 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); - ClassDB::bind_method(D_METHOD("set_network_peer", "peer"), &SceneTree::set_network_peer); - ClassDB::bind_method(D_METHOD("get_network_peer"), &SceneTree::get_network_peer); - ClassDB::bind_method(D_METHOD("is_network_server"), &SceneTree::is_network_server); - ClassDB::bind_method(D_METHOD("has_network_peer"), &SceneTree::has_network_peer); - ClassDB::bind_method(D_METHOD("get_network_connected_peers"), &SceneTree::get_network_connected_peers); - ClassDB::bind_method(D_METHOD("get_network_unique_id"), &SceneTree::get_network_unique_id); - ClassDB::bind_method(D_METHOD("get_rpc_sender_id"), &SceneTree::get_rpc_sender_id); - ClassDB::bind_method(D_METHOD("set_refuse_new_network_connections", "refuse"), &SceneTree::set_refuse_new_network_connections); - ClassDB::bind_method(D_METHOD("is_refusing_new_network_connections"), &SceneTree::is_refusing_new_network_connections); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "debug_collisions_hint"), "set_debug_collisions_hint", "is_debugging_collisions_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::BOOL, "refuse_new_network_connections"), "set_refuse_new_network_connections", "is_refusing_new_network_connections"); - ADD_PROPERTY_DEFAULT("refuse_new_network_connections", false); 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, "network_peer", PROPERTY_HINT_RESOURCE_TYPE, "NetworkedMultiplayerPeer", PROPERTY_USAGE_NONE), "set_network_peer", "get_network_peer"); 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"); @@ -1330,11 +1250,6 @@ void SceneTree::_bind_methods() { ADD_SIGNAL(MethodInfo("physics_frame")); ADD_SIGNAL(MethodInfo("files_dropped", PropertyInfo(Variant::PACKED_STRING_ARRAY, "files"), PropertyInfo(Variant::INT, "screen"))); - ADD_SIGNAL(MethodInfo("network_peer_connected", PropertyInfo(Variant::INT, "id"))); - ADD_SIGNAL(MethodInfo("network_peer_disconnected", PropertyInfo(Variant::INT, "id"))); - ADD_SIGNAL(MethodInfo("connected_to_server")); - ADD_SIGNAL(MethodInfo("connection_failed")); - ADD_SIGNAL(MethodInfo("server_disconnected")); BIND_ENUM_CONSTANT(GROUP_CALL_DEFAULT); BIND_ENUM_CONSTANT(GROUP_CALL_REVERSE); @@ -1408,20 +1323,21 @@ SceneTree::SceneTree() { root = memnew(Window); root->set_name("root"); +#ifndef _3D_DISABLED if (!root->get_world_3d().is_valid()) { root->set_world_3d(Ref<World3D>(memnew(World3D))); } + root->set_as_audio_listener_3d(true); +#endif // _3D_DISABLED // Initialize network state. set_multiplayer(Ref<MultiplayerAPI>(memnew(MultiplayerAPI))); - //root->set_world_2d( Ref<World2D>( memnew( World2D ))); - root->set_as_audio_listener(true); root->set_as_audio_listener_2d(true); current_scene = nullptr; const int msaa_mode = GLOBAL_DEF("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, "Disabled (Fastest),2x (Fast),4x (Average),8x (Slow),16x (Slower)")); + 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); @@ -1472,16 +1388,17 @@ SceneTree::SceneTree() { ProjectSettings::get_singleton()->set_custom_property_info("rendering/2d/sdf/oversize", PropertyInfo(Variant::INT, "rendering/2d/sdf/oversize", PROPERTY_HINT_ENUM, "100%,120%,150%,200%")); ProjectSettings::get_singleton()->set_custom_property_info("rendering/2d/sdf/scale", PropertyInfo(Variant::INT, "rendering/2d/sdf/scale", PROPERTY_HINT_ENUM, "100%,50%,25%")); +#ifndef _3D_DISABLED { // Load default fallback environment. // Get possible extensions. List<String> exts; ResourceLoader::get_recognized_extensions_for_type("Environment", &exts); String ext_hint; - for (List<String>::Element *E = exts.front(); E; E = E->next()) { + for (const String &E : exts) { if (ext_hint != String()) { ext_hint += ","; } - ext_hint += "*." + E->get(); + ext_hint += "*." + E; } // Get path. String env_path = GLOBAL_DEF("rendering/environment/defaults/default_environment", ""); @@ -1503,6 +1420,7 @@ SceneTree::SceneTree() { } } } +#endif // _3D_DISABLED root->set_physics_object_picking(GLOBAL_DEF("physics/common/enable_object_picking", true)); diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index 0e9ffb0f5f..19331c1906 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -31,7 +31,7 @@ #ifndef SCENE_TREE_H #define SCENE_TREE_H -#include "core/io/multiplayer_api.h" +#include "core/multiplayer/multiplayer_api.h" #include "core/os/main_loop.h" #include "core/os/thread_safe.h" #include "core/templates/self_list.h" @@ -52,19 +52,23 @@ class Tween; class SceneTreeTimer : public RefCounted { GDCLASS(SceneTreeTimer, RefCounted); - float time_left = 0.0; + double time_left = 0.0; bool process_always = true; + bool ignore_time_scale = false; protected: static void _bind_methods(); public: - void set_time_left(float p_time); - float get_time_left() const; + void set_time_left(double p_time); + double get_time_left() const; void set_process_always(bool p_process_always); bool is_process_always(); + void set_ignore_time_scale(bool p_ignore); + bool is_ignore_time_scale(); + void release_connections(); SceneTreeTimer(); @@ -87,8 +91,8 @@ private: Window *root = nullptr; uint64_t tree_version = 1; - float physics_process_time = 1.0; - float process_time = 1.0; + double physics_process_time = 1.0; + double process_time = 1.0; bool accept_quit = true; bool quit_on_go_back = true; @@ -159,13 +163,6 @@ private: Ref<MultiplayerAPI> multiplayer; bool multiplayer_poll = true; - void _network_peer_connected(int p_id); - void _network_peer_disconnected(int p_id); - - void _connected_to_server(); - void _connection_failed(); - void _server_disconnected(); - static SceneTree *singleton; friend class Node; @@ -207,8 +204,14 @@ private: void _main_window_close(); void _main_window_go_back(); + enum CallInputType { + CALL_INPUT_TYPE_INPUT, + CALL_INPUT_TYPE_UNHANDLED_INPUT, + CALL_INPUT_TYPE_UNHANDLED_KEY_INPUT, + }; + //used by viewport - void _call_input_pause(const StringName &p_group, const StringName &p_method, const Ref<InputEvent> &p_input, Viewport *p_viewport); + void _call_input_pause(const StringName &p_group, CallInputType p_call_type, const Ref<InputEvent> &p_input, Viewport *p_viewport); protected: void _notification(int p_notification); @@ -240,8 +243,8 @@ public: virtual void initialize() override; - virtual bool physics_process(float p_time) override; - virtual bool process(float p_time) override; + virtual bool physics_process(double p_time) override; + virtual bool process(double p_time) override; virtual void finalize() override; @@ -250,8 +253,8 @@ public: void quit(int p_exit_code = EXIT_SUCCESS); - _FORCE_INLINE_ float get_physics_process_time() const { return physics_process_time; } - _FORCE_INLINE_ float get_process_time() const { return process_time; } + _FORCE_INLINE_ double get_physics_process_time() const { return physics_process_time; } + _FORCE_INLINE_ double get_process_time() const { return process_time; } #ifdef TOOLS_ENABLED bool is_node_being_edited(const Node *p_node) const; @@ -320,7 +323,7 @@ public: Error change_scene_to(const Ref<PackedScene> &p_scene); Error reload_current_scene(); - Ref<SceneTreeTimer> create_timer(float p_delay_sec, bool p_process_always = true); + Ref<SceneTreeTimer> create_timer(double p_delay_sec, bool p_process_always = true); Ref<Tween> create_tween(); Array get_processed_tweens(); @@ -337,16 +340,6 @@ public: void set_multiplayer_poll_enabled(bool p_enabled); bool is_multiplayer_poll_enabled() const; void set_multiplayer(Ref<MultiplayerAPI> p_multiplayer); - void set_network_peer(const Ref<NetworkedMultiplayerPeer> &p_network_peer); - Ref<NetworkedMultiplayerPeer> get_network_peer() const; - bool is_network_server() const; - bool has_network_peer() const; - int get_network_unique_id() const; - Vector<int> get_network_connected_peers() const; - int get_rpc_sender_id() const; - - void set_refuse_new_network_connections(bool p_refuse); - bool is_refusing_new_network_connections() const; 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 3d65c12cb7..9477e300d1 100644 --- a/scene/main/shader_globals_override.cpp +++ b/scene/main/shader_globals_override.cpp @@ -30,8 +30,7 @@ #include "shader_globals_override.h" -#include "core/core_string_names.h" -#include "scene/main/window.h" +#include "scene/3d/node_3d.h" #include "scene/scene_string_names.h" StringName *ShaderGlobalsOverride::_remap(const StringName &p_name) const { @@ -63,7 +62,12 @@ bool ShaderGlobalsOverride::_set(const StringName &p_name, const Variant &p_valu if (o) { o->override = p_value; if (active) { - RS::get_singleton()->global_variable_set_override(*r, p_value); + if (o->override.get_type() == Variant::OBJECT) { + RID tex_rid = p_value; + RS::get_singleton()->global_variable_set_override(*r, tex_rid); + } else { + RS::get_singleton()->global_variable_set_override(*r, p_value); + } } o->in_use = p_value.get_type() != Variant::NIL; return true; @@ -228,7 +232,12 @@ void ShaderGlobalsOverride::_activate() { while ((K = overrides.next(K))) { Override *o = overrides.getptr(*K); if (o->in_use && o->override.get_type() != Variant::NIL) { - RS::get_singleton()->global_variable_set_override(*K, o->override); + if (o->override.get_type() == Variant::OBJECT) { + RID tex_rid = o->override; + RS::get_singleton()->global_variable_set_override(*K, tex_rid); + } else { + RS::get_singleton()->global_variable_set_override(*K, o->override); + } } } diff --git a/scene/main/shader_globals_override.h b/scene/main/shader_globals_override.h index 2d9c3c76bd..ab4b9de727 100644 --- a/scene/main/shader_globals_override.h +++ b/scene/main/shader_globals_override.h @@ -31,7 +31,7 @@ #ifndef SHADER_GLOBALS_OVERRIDE_H #define SHADER_GLOBALS_OVERRIDE_H -#include "scene/3d/node_3d.h" +#include "scene/main/node.h" class ShaderGlobalsOverride : public Node { GDCLASS(ShaderGlobalsOverride, Node); diff --git a/scene/main/timer.cpp b/scene/main/timer.cpp index f52237251c..9e462eb1c8 100644 --- a/scene/main/timer.cpp +++ b/scene/main/timer.cpp @@ -30,8 +30,6 @@ #include "timer.h" -#include "core/config/engine.h" - void Timer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_READY: { @@ -58,7 +56,7 @@ void Timer::_notification(int p_what) { stop(); } - emit_signal("timeout"); + emit_signal(SNAME("timeout")); } } break; @@ -74,19 +72,19 @@ void Timer::_notification(int p_what) { } else { stop(); } - emit_signal("timeout"); + emit_signal(SNAME("timeout")); } } break; } } -void Timer::set_wait_time(float p_time) { +void Timer::set_wait_time(double p_time) { ERR_FAIL_COND_MSG(p_time <= 0, "Time should be greater than zero."); wait_time = p_time; } -float Timer::get_wait_time() const { +double Timer::get_wait_time() const { return wait_time; } @@ -106,7 +104,7 @@ bool Timer::has_autostart() const { return autostart; } -void Timer::start(float p_time) { +void Timer::start(double p_time) { ERR_FAIL_COND_MSG(!is_inside_tree(), "Timer was not added to the SceneTree. Either add it or set autostart to true."); if (p_time > 0) { @@ -139,7 +137,7 @@ bool Timer::is_stopped() const { return get_time_left() <= 0; } -float Timer::get_time_left() const { +double Timer::get_time_left() const { return time_left > 0 ? time_left : 0; } diff --git a/scene/main/timer.h b/scene/main/timer.h index 3d9e21d7fc..2b9faddcb9 100644 --- a/scene/main/timer.h +++ b/scene/main/timer.h @@ -36,7 +36,7 @@ class Timer : public Node { GDCLASS(Timer, Node); - float wait_time = 1.0; + double wait_time = 1.0; bool one_shot = false; bool autostart = false; bool processing = false; @@ -54,8 +54,8 @@ public: TIMER_PROCESS_IDLE, }; - void set_wait_time(float p_time); - float get_wait_time() const; + void set_wait_time(double p_time); + double get_wait_time() const; void set_one_shot(bool p_one_shot); bool is_one_shot() const; @@ -63,7 +63,7 @@ public: void set_autostart(bool p_start); bool has_autostart() const; - void start(float p_time = -1); + void start(double p_time = -1); void stop(); void set_paused(bool p_paused); @@ -71,7 +71,7 @@ public: bool is_stopped() const; - float get_time_left() const; + double get_time_left() const; void set_timer_process_callback(TimerProcessCallback p_callback); TimerProcessCallback get_timer_process_callback() const; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 9a2e0c5230..fb86d37280 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -30,32 +30,31 @@ #include "viewport.h" -#include "core/config/project_settings.h" #include "core/core_string_names.h" #include "core/debugger/engine_debugger.h" -#include "core/input/input.h" -#include "core/os/os.h" +#include "core/object/message_queue.h" #include "core/string/translation.h" - +#include "core/templates/pair.h" +#include "scene/2d/camera_2d.h" #include "scene/2d/collision_object_2d.h" +#include "scene/2d/listener_2d.h" +#ifndef _3D_DISABLED #include "scene/3d/camera_3d.h" #include "scene/3d/collision_object_3d.h" #include "scene/3d/listener_3d.h" -#include "scene/3d/node_3d.h" #include "scene/3d/world_environment.h" +#endif // _3D_DISABLED #include "scene/gui/control.h" #include "scene/gui/label.h" -#include "scene/gui/menu_button.h" -#include "scene/gui/panel.h" -#include "scene/gui/panel_container.h" +#include "scene/gui/popup.h" #include "scene/gui/popup_menu.h" #include "scene/main/canvas_layer.h" -#include "scene/main/timer.h" #include "scene/main/window.h" #include "scene/resources/mesh.h" +#include "scene/resources/text_line.h" +#include "scene/resources/world_2d.h" #include "scene/scene_string_names.h" -#include "servers/display_server.h" -#include "servers/physics_server_2d.h" +#include "servers/audio_server.h" void ViewportTexture::setup_local_to_scene() { if (vp) { @@ -82,7 +81,7 @@ void ViewportTexture::setup_local_to_scene() { RS::get_singleton()->texture_proxy_update(proxy, vp->texture_rid); RS::get_singleton()->free(proxy_ph); } else { - ERR_FAIL_COND(proxy.is_valid()); //should be invalid + ERR_FAIL_COND(proxy.is_valid()); // Should be invalid. proxy = RS::get_singleton()->texture_proxy_create(vp->texture_rid); } } @@ -119,7 +118,6 @@ Size2 ViewportTexture::get_size() const { } RID ViewportTexture::get_rid() const { - //ERR_FAIL_COND_V_MSG(!vp, RID(), "Viewport Texture must be set to use it."); if (proxy.is_null()) { proxy_ph = RS::get_singleton()->texture_2d_placeholder_create(); proxy = RS::get_singleton()->texture_proxy_create(proxy_ph); @@ -183,24 +181,6 @@ public: ///////////////////////////////////// -void Viewport::_collision_object_input_event(CollisionObject3D *p_object, Camera3D *p_camera, const Ref<InputEvent> &p_input_event, const Vector3 &p_pos, const Vector3 &p_normal, int p_shape) { - Transform3D object_transform = p_object->get_global_transform(); - Transform3D camera_transform = p_camera->get_global_transform(); - ObjectID id = p_object->get_instance_id(); - - //avoid sending the fake event unnecessarily if nothing really changed in the context - if (object_transform == physics_last_object_transform && camera_transform == physics_last_camera_transform && physics_last_id == id) { - Ref<InputEventMouseMotion> mm = p_input_event; - if (mm.is_valid() && mm->get_device() == InputEvent::DEVICE_ID_INTERNAL) { - return; //discarded - } - } - p_object->_input_event(camera, p_input_event, p_pos, p_normal, p_shape); - physics_last_object_transform = object_transform; - physics_last_camera_transform = camera_transform; - physics_last_id = id; -} - 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); @@ -248,33 +228,32 @@ void Viewport::_sub_window_update(Window *p_window) { Rect2i r = Rect2i(p_window->get_position(), sw.window->get_size()); if (!p_window->get_flag(Window::FLAG_BORDERLESS)) { - Ref<StyleBox> panel = p_window->get_theme_stylebox("panel_window"); + Ref<StyleBox> panel = p_window->get_theme_stylebox(SNAME("embedded_border")); panel->draw(sw.canvas_item, r); // Draw the title bar text. - Ref<Font> title_font = p_window->get_theme_font("title_font"); - int font_size = p_window->get_theme_font_size("title_font_size"); - Color title_color = p_window->get_theme_color("title_color"); - int title_height = p_window->get_theme_constant("title_height"); - int close_h_ofs = p_window->get_theme_constant("close_h_ofs"); - int close_v_ofs = p_window->get_theme_constant("close_v_ofs"); - - TextLine title_text = TextLine(p_window->get_title(), title_font, font_size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); + Ref<Font> title_font = p_window->get_theme_font(SNAME("title_font")); + 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")); + + TextLine title_text = TextLine(p_window->atr(p_window->get_title()), title_font, font_size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); 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; int y = (-title_height - title_text.get_size().y) / 2; - Color font_outline_color = p_window->get_theme_color("title_outline_modulate"); - int outline_size = p_window->get_theme_constant("title_outline_size"); + Color font_outline_color = p_window->get_theme_color(SNAME("title_outline_modulate")); + int outline_size = p_window->get_theme_constant(SNAME("title_outline_size")); if (outline_size > 0 && font_outline_color.a > 0) { title_text.draw_outline(sw.canvas_item, r.position + Point2(x, y), outline_size, font_outline_color); } title_text.draw(sw.canvas_item, r.position + Point2(x, y), title_color); - bool hl = gui.subwindow_focused == sw.window && gui.subwindow_drag == SUB_WINDOW_DRAG_CLOSE && gui.subwindow_drag_close_inside; - - Ref<Texture2D> close_icon = p_window->get_theme_icon(hl ? "close_highlight" : "close"); + bool pressed = gui.subwindow_focused == sw.window && gui.subwindow_drag == SUB_WINDOW_DRAG_CLOSE && gui.subwindow_drag_close_inside; + Ref<Texture2D> close_icon = p_window->get_theme_icon(pressed ? "close_pressed" : "close"); close_icon->draw(sw.canvas_item, r.position + Vector2(r.size.width - close_h_ofs, -close_v_ofs)); } @@ -283,7 +262,7 @@ void Viewport::_sub_window_update(Window *p_window) { void Viewport::_sub_window_grab_focus(Window *p_window) { if (p_window == nullptr) { - //release current focus + // Release current focus. if (gui.subwindow_focused) { gui.subwindow_focused->_event_callback(DisplayServer::WINDOW_EVENT_FOCUS_OUT); gui.subwindow_focused = nullptr; @@ -309,18 +288,18 @@ void Viewport::_sub_window_grab_focus(Window *p_window) { ERR_FAIL_COND(index == -1); if (p_window->get_flag(Window::FLAG_NO_FOCUS)) { - //can only move to foreground, but no focus granted + // Can only move to foreground, but no focus granted. SubWindow sw = gui.sub_windows[index]; gui.sub_windows.remove(index); gui.sub_windows.push_back(sw); index = gui.sub_windows.size() - 1; _sub_window_update_order(); - return; //i guess not... + return; } if (gui.subwindow_focused) { if (gui.subwindow_focused == p_window) { - return; //nothing to do + return; // Nothing to do. } gui.subwindow_focused->_event_callback(DisplayServer::WINDOW_EVENT_FOCUS_OUT); gui.subwindow_drag = SUB_WINDOW_DRAG_DISABLED; @@ -337,7 +316,7 @@ void Viewport::_sub_window_grab_focus(Window *p_window) { gui.subwindow_focused->_event_callback(DisplayServer::WINDOW_EVENT_FOCUS_IN); - { //move to foreground + { // Move to foreground. SubWindow sw = gui.sub_windows[index]; gui.sub_windows.remove(index); gui.sub_windows.push_back(sw); @@ -388,27 +367,6 @@ void Viewport::_sub_window_remove(Window *p_window) { RenderingServer::get_singleton()->viewport_set_parent_viewport(p_window->viewport, p_window->parent ? p_window->parent->viewport : RID()); } -void Viewport::_own_world_3d_changed() { - ERR_FAIL_COND(world_3d.is_null()); - ERR_FAIL_COND(own_world_3d.is_null()); - - if (is_inside_tree()) { - _propagate_exit_world(this); - } - - own_world_3d = world_3d->duplicate(); - - if (is_inside_tree()) { - _propagate_enter_world(this); - } - - if (is_inside_tree()) { - RenderingServer::get_singleton()->viewport_set_scenario(viewport, find_world_3d()->get_scenario()); - } - - _update_listener(); -} - void Viewport::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { @@ -422,19 +380,19 @@ void Viewport::_notification(int p_what) { } current_canvas = find_world_2d()->get_canvas(); - RenderingServer::get_singleton()->viewport_set_scenario(viewport, find_world_3d()->get_scenario()); RenderingServer::get_singleton()->viewport_attach_canvas(viewport, current_canvas); - - _update_listener(); _update_listener_2d(); +#ifndef _3D_DISABLED + RenderingServer::get_singleton()->viewport_set_scenario(viewport, find_world_3d()->get_scenario()); + _update_listener_3d(); +#endif // _3D_DISABLED add_to_group("_viewports"); if (get_tree()->is_debugging_collisions_hint()) { - //2D PhysicsServer2D::get_singleton()->space_set_debug_contacts(find_world_2d()->get_space(), get_tree()->get_collision_debug_contact_count()); contact_2d_debug = RenderingServer::get_singleton()->canvas_item_create(); RenderingServer::get_singleton()->canvas_item_set_parent(contact_2d_debug, find_world_2d()->get_canvas()); - //3D +#ifndef _3D_DISABLED PhysicsServer3D::get_singleton()->space_set_debug_contacts(find_world_3d()->get_space(), get_tree()->get_collision_debug_contact_count()); contact_3d_debug_multimesh = RenderingServer::get_singleton()->multimesh_create(); RenderingServer::get_singleton()->multimesh_allocate_data(contact_3d_debug_multimesh, get_tree()->get_collision_debug_contact_count(), RS::MULTIMESH_TRANSFORM_3D, true); @@ -444,14 +402,15 @@ void Viewport::_notification(int p_what) { RenderingServer::get_singleton()->instance_set_base(contact_3d_debug_instance, contact_3d_debug_multimesh); RenderingServer::get_singleton()->instance_set_scenario(contact_3d_debug_instance, find_world_3d()->get_scenario()); //RenderingServer::get_singleton()->instance_geometry_set_flag(contact_3d_debug_instance, RS::INSTANCE_FLAG_VISIBLE_IN_ALL_ROOMS, true); +#endif // _3D_DISABLED } } break; case NOTIFICATION_READY: { #ifndef _3D_DISABLED - if (listeners.size() && !listener) { + if (listener_3d_set.size() && !listener_3d) { Listener3D *first = nullptr; - for (Set<Listener3D *>::Element *E = listeners.front(); E; E = E->next()) { + for (Set<Listener3D *>::Element *E = listener_3d_set.front(); E; E = E->next()) { if (first == nullptr || first->is_greater_than(E->get())) { first = E->get(); } @@ -462,10 +421,10 @@ void Viewport::_notification(int p_what) { } } - if (cameras.size() && !camera) { - //there are cameras but no current camera, pick first in tree and make it current + 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 = cameras.front(); E; E = E->next()) { + 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(); } @@ -475,12 +434,7 @@ void Viewport::_notification(int p_what) { first->make_current(); } } -#endif - - // Enable processing for tooltips, collision debugging, physics object picking, etc. - set_process_internal(true); - set_physics_process_internal(true); - +#endif // _3D_DISABLED } break; case NOTIFICATION_EXIT_TREE: { _gui_cancel_tooltip(); @@ -500,19 +454,10 @@ void Viewport::_notification(int p_what) { } remove_from_group("_viewports"); + set_physics_process_internal(false); RS::get_singleton()->viewport_set_active(viewport, false); RenderingServer::get_singleton()->viewport_set_parent_viewport(viewport, RID()); - - } break; - case NOTIFICATION_INTERNAL_PROCESS: { - if (gui.tooltip_timer >= 0) { - gui.tooltip_timer -= get_process_delta_time(); - if (gui.tooltip_timer < 0) { - _gui_show_tooltip(); - } - } - } break; case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { if (get_tree()->is_debugging_collisions_hint() && contact_2d_debug.is_valid()) { @@ -527,7 +472,7 @@ void Viewport::_notification(int p_what) { RenderingServer::get_singleton()->canvas_item_add_rect(contact_2d_debug, Rect2(points[i] - Vector2(2, 2), Vector2(5, 5)), ccol); } } - +#ifndef _3D_DISABLED if (get_tree()->is_debugging_collisions_hint() && contact_3d_debug_multimesh.is_valid()) { Vector<Vector3> points = PhysicsServer3D::get_singleton()->space_get_contacts(find_world_3d()->get_space()); int point_count = PhysicsServer3D::get_singleton()->space_get_contact_count(find_world_3d()->get_space()); @@ -540,6 +485,7 @@ void Viewport::_notification(int p_what) { RS::get_singleton()->multimesh_instance_set_transform(contact_3d_debug_multimesh, i, point_transform); } } +#endif // _3D_DISABLED } break; case NOTIFICATION_WM_MOUSE_EXIT: { _drop_physics_mouseover(); @@ -572,20 +518,14 @@ void Viewport::_process_picking() { _drop_physics_mouseover(true); -#ifndef _3D_DISABLED - Vector2 last_pos(1e20, 1e20); - CollisionObject3D *last_object = nullptr; - ObjectID last_id; -#endif - PhysicsDirectSpaceState3D::RayResult result; PhysicsDirectSpaceState2D *ss2d = PhysicsServer2D::get_singleton()->space_get_direct_state(find_world_2d()->get_space()); if (physics_has_last_mousepos) { - // if no mouse event exists, create a motion one. This is necessary because objects or camera may have moved. - // while this extra event is sent, it is checked if both camera and last object and last ID did not move. If nothing changed, the event is discarded to avoid flooding with unnecessary motion events every frame + // If no mouse event exists, create a motion one. This is necessary because objects or camera may have moved. + // While this extra event is sent, it is checked if both camera and last object and last ID did not move. + // If nothing changed, the event is discarded to avoid flooding with unnecessary motion events every frame. bool has_mouse_event = false; - for (List<Ref<InputEvent>>::Element *E = physics_picking_events.front(); E; E = E->next()) { - Ref<InputEventMouse> m = E->get(); + for (const Ref<InputEvent> &m : physics_picking_events) { if (m.is_valid()) { has_mouse_event = true; break; @@ -657,7 +597,7 @@ void Viewport::_process_picking() { Ref<InputEventKey> k = ev; if (k.is_valid()) { - //only for mask + // Only for mask. physics_last_mouse_state.alt = k->is_alt_pressed(); physics_last_mouse_state.shift = k->is_shift_pressed(); physics_last_mouse_state.control = k->is_ctrl_pressed(); @@ -678,7 +618,7 @@ void Viewport::_process_picking() { } if (ss2d) { - //send to 2D + // Send to 2D. uint64_t frame = get_tree()->get_frame(); @@ -687,11 +627,11 @@ void Viewport::_process_picking() { Transform2D canvas_transform; ObjectID canvas_layer_id; if (E->get()) { - // A descendant CanvasLayer + // A descendant CanvasLayer. canvas_transform = E->get()->get_transform(); canvas_layer_id = E->get()->get_instance_id(); } else { - // This Viewport's builtin canvas + // This Viewport's builtin canvas. canvas_transform = get_canvas_transform(); canvas_layer_id = ObjectID(); } @@ -706,21 +646,27 @@ void Viewport::_process_picking() { bool send_event = true; if (is_mouse) { Map<ObjectID, uint64_t>::Element *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; - // It was already hovered, so don't send the event if it's faked + // 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)); + 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; + } } if (send_event) { - co->_input_event(this, ev, res[i].shape); + co->_input_event_call(this, ev, res[i].shape); } } } @@ -728,35 +674,21 @@ void Viewport::_process_picking() { } if (is_mouse) { - List<Map<ObjectID, uint64_t>::Element *> to_erase; - - for (Map<ObjectID, uint64_t>::Element *E = physics_2d_mouseover.front(); E; E = E->next()) { - if (E->get() != frame) { - Object *o = ObjectDB::get_instance(E->key()); - if (o) { - CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o); - if (co) { - co->_mouse_exit(); - } - } - to_erase.push_back(E); - } - } - - while (to_erase.size()) { - physics_2d_mouseover.erase(to_erase.front()->get()); - to_erase.pop_front(); - } + _cleanup_mouseover_colliders(false, false, frame); } } #ifndef _3D_DISABLED + Vector2 last_pos(1e20, 1e20); + CollisionObject3D *last_object = nullptr; + ObjectID last_id; + PhysicsDirectSpaceState3D::RayResult result; bool captured = false; if (physics_object_capture.is_valid()) { CollisionObject3D *co = Object::cast_to<CollisionObject3D>(ObjectDB::get_instance(physics_object_capture)); - if (co && camera) { - _collision_object_input_event(co, camera, ev, Vector3(), Vector3(), 0); + if (co && camera_3d) { + _collision_object_3d_input_event(co, camera_3d, ev, Vector3(), Vector3(), 0); captured = true; if (mb.is_valid() && mb->get_button_index() == 1 && !mb->is_pressed()) { physics_object_capture = ObjectID(); @@ -768,21 +700,21 @@ void Viewport::_process_picking() { } if (captured) { - //none + // None. } else if (pos == last_pos) { if (last_id.is_valid()) { if (ObjectDB::get_instance(last_id) && last_object) { - //good, exists - _collision_object_input_event(last_object, camera, ev, result.position, result.normal, result.shape); + // Good, exists. + _collision_object_3d_input_event(last_object, camera_3d, ev, result.position, result.normal, result.shape); if (last_object->get_capture_input_on_drag() && mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed()) { physics_object_capture = last_id; } } } } else { - if (camera) { - Vector3 from = camera->project_ray_origin(pos); - Vector3 dir = camera->project_ray_normal(pos); + if (camera_3d) { + Vector3 from = camera_3d->project_ray_origin(pos); + Vector3 dir = camera_3d->project_ray_normal(pos); PhysicsDirectSpaceState3D *space = PhysicsServer3D::get_singleton()->space_get_direct_state(find_world_3d()->get_space()); if (space) { @@ -791,7 +723,7 @@ void Viewport::_process_picking() { if (col) { CollisionObject3D *co = Object::cast_to<CollisionObject3D>(result.collider); if (co && co->can_process()) { - _collision_object_input_event(co, camera, ev, result.position, result.normal, result.shape); + _collision_object_3d_input_event(co, camera_3d, ev, result.position, result.normal, result.shape); last_object = co; last_id = result.collider_id; new_collider = last_id; @@ -823,7 +755,7 @@ void Viewport::_process_picking() { last_pos = pos; } } -#endif +#endif // _3D_DISABLED } } @@ -859,7 +791,7 @@ void Viewport::_set_size(const Size2i &p_size, const Size2i &p_size_2d_override, update_canvas_items(); - emit_signal("size_changed"); + emit_signal(SNAME("size_changed")); } Size2i Viewport::_get_size() const { @@ -890,29 +822,10 @@ Rect2 Viewport::get_visible_rect() const { return r; } -void Viewport::_update_listener() { -} - void Viewport::_update_listener_2d() { - /* - if (is_inside_tree() && audio_listener && (!get_parent() || (Object::cast_to<Control>(get_parent()) && Object::cast_to<Control>(get_parent())->is_visible_in_tree()))) - SpatialSound2DServer::get_singleton()->listener_set_space(internal_listener_2d, find_world_2d()->get_sound_space()); - else - SpatialSound2DServer::get_singleton()->listener_set_space(internal_listener_2d, RID()); -*/ -} - -void Viewport::set_as_audio_listener(bool p_enable) { - if (p_enable == audio_listener) { - return; + if (AudioServer::get_singleton()) { + AudioServer::get_singleton()->notify_listener_changed(); } - - audio_listener = p_enable; - _update_listener(); -} - -bool Viewport::is_audio_listener() const { - return audio_listener; } void Viewport::set_as_audio_listener_2d(bool p_enable) { @@ -921,7 +834,6 @@ void Viewport::set_as_audio_listener_2d(bool p_enable) { } audio_listener_2d = p_enable; - _update_listener_2d(); } @@ -929,13 +841,8 @@ bool Viewport::is_audio_listener_2d() const { return audio_listener_2d; } -void Viewport::set_disable_3d(bool p_disable) { - disable_3d = p_disable; - RenderingServer::get_singleton()->viewport_set_disable_3d(viewport, disable_3d); -} - -bool Viewport::is_3d_disabled() const { - return disable_3d; +Listener2D *Viewport::get_listener_2d() const { + return listener_2d; } void Viewport::enable_canvas_transform_override(bool p_enable) { @@ -998,127 +905,25 @@ Transform2D Viewport::get_global_canvas_transform() const { return global_canvas_transform; } -void Viewport::_listener_transform_changed_notify() { +void Viewport::_camera_2d_set(Camera2D *p_camera_2d) { + camera_2d = p_camera_2d; } -void Viewport::_listener_set(Listener3D *p_listener) { -#ifndef _3D_DISABLED - - if (listener == p_listener) { +void Viewport::_listener_2d_set(Listener2D *p_listener) { + if (listener_2d == p_listener) { return; + } else if (listener_2d) { + listener_2d->clear_current(); } - - listener = p_listener; - - _update_listener(); - _listener_transform_changed_notify(); -#endif + listener_2d = p_listener; } -bool Viewport::_listener_add(Listener3D *p_listener) { - listeners.insert(p_listener); - return listeners.size() == 1; -} - -void Viewport::_listener_remove(Listener3D *p_listener) { - listeners.erase(p_listener); - if (listener == p_listener) { - listener = nullptr; +void Viewport::_listener_2d_remove(Listener2D *p_listener) { + if (listener_2d == p_listener) { + listener_2d = nullptr; } } -#ifndef _3D_DISABLED -void Viewport::_listener_make_next_current(Listener3D *p_exclude) { - if (listeners.size() > 0) { - for (Set<Listener3D *>::Element *E = listeners.front(); E; E = E->next()) { - if (p_exclude == E->get()) { - continue; - } - if (!E->get()->is_inside_tree()) { - continue; - } - if (listener != nullptr) { - return; - } - - E->get()->make_current(); - } - } else { - // Attempt to reset listener to the camera position - if (camera != nullptr) { - _update_listener(); - _camera_transform_changed_notify(); - } - } -} -#endif - -void Viewport::_camera_transform_changed_notify() { -#ifndef _3D_DISABLED -#endif -} - -void Viewport::_camera_set(Camera3D *p_camera) { -#ifndef _3D_DISABLED - - if (camera == p_camera) { - return; - } - - if (camera) { - camera->notification(Camera3D::NOTIFICATION_LOST_CURRENT); - } - - camera = p_camera; - - if (!camera_override) { - if (camera) { - RenderingServer::get_singleton()->viewport_attach_camera(viewport, camera->get_camera()); - } else { - RenderingServer::get_singleton()->viewport_attach_camera(viewport, RID()); - } - } - - if (camera) { - camera->notification(Camera3D::NOTIFICATION_BECAME_CURRENT); - } - - _update_listener(); - _camera_transform_changed_notify(); -#endif -} - -bool Viewport::_camera_add(Camera3D *p_camera) { - cameras.insert(p_camera); - return cameras.size() == 1; -} - -void Viewport::_camera_remove(Camera3D *p_camera) { - cameras.erase(p_camera); - if (camera == p_camera) { - camera->notification(Camera3D::NOTIFICATION_LOST_CURRENT); - camera = nullptr; - } -} - -#ifndef _3D_DISABLED -void Viewport::_camera_make_next_current(Camera3D *p_exclude) { - for (Set<Camera3D *>::Element *E = cameras.front(); E; E = E->next()) { - if (p_exclude == E->get()) { - continue; - } - if (!E->get()->is_inside_tree()) { - continue; - } - if (camera != nullptr) { - return; - } - - E->get()->make_current(); - } -} -#endif - void Viewport::_canvas_layer_add(CanvasLayer *p_canvas_layer) { canvas_layers.insert(p_canvas_layer); } @@ -1142,7 +947,7 @@ void Viewport::set_world_2d(const Ref<World2D> &p_world_2d) { } if (parent && parent->find_world_2d() == p_world_2d) { - WARN_PRINT("Unable to use parent world_3d as world_2d"); + WARN_PRINT("Unable to use parent world_2d as world_2d"); return; } @@ -1175,33 +980,6 @@ Ref<World2D> Viewport::find_world_2d() const { } } -void Viewport::_propagate_enter_world(Node *p_node) { - if (p_node != this) { - if (!p_node->is_inside_tree()) { //may not have entered scene yet - return; - } - -#ifndef _3D_DISABLED - if (Object::cast_to<Node3D>(p_node) || Object::cast_to<WorldEnvironment>(p_node)) { - p_node->notification(Node3D::NOTIFICATION_ENTER_WORLD); - } else { -#endif - Viewport *v = Object::cast_to<Viewport>(p_node); - if (v) { - if (v->world_3d.is_valid() || v->own_world_3d.is_valid()) { - return; - } - } -#ifndef _3D_DISABLED - } -#endif - } - - for (int i = 0; i < p_node->get_child_count(); i++) { - _propagate_enter_world(p_node->get_child(i)); - } -} - void Viewport::_propagate_viewport_notification(Node *p_node, int p_what) { p_node->notification(p_what); for (int i = 0; i < p_node->get_child_count(); i++) { @@ -1213,168 +991,12 @@ void Viewport::_propagate_viewport_notification(Node *p_node, int p_what) { } } -void Viewport::_propagate_exit_world(Node *p_node) { - if (p_node != this) { - if (!p_node->is_inside_tree()) { //may have exited scene already - return; - } - -#ifndef _3D_DISABLED - if (Object::cast_to<Node3D>(p_node) || Object::cast_to<WorldEnvironment>(p_node)) { - p_node->notification(Node3D::NOTIFICATION_EXIT_WORLD); - } else { -#endif - Viewport *v = Object::cast_to<Viewport>(p_node); - if (v) { - if (v->world_3d.is_valid() || v->own_world_3d.is_valid()) { - return; - } - } -#ifndef _3D_DISABLED - } -#endif - } - - for (int i = 0; i < p_node->get_child_count(); i++) { - _propagate_exit_world(p_node->get_child(i)); - } -} - -void Viewport::set_world_3d(const Ref<World3D> &p_world_3d) { - if (world_3d == p_world_3d) { - return; - } - - if (is_inside_tree()) { - _propagate_exit_world(this); - } - - if (own_world_3d.is_valid() && world_3d.is_valid()) { - world_3d->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Viewport::_own_world_3d_changed)); - } - - world_3d = p_world_3d; - - if (own_world_3d.is_valid()) { - 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)); - } - } - - if (is_inside_tree()) { - _propagate_enter_world(this); - } - - if (is_inside_tree()) { - RenderingServer::get_singleton()->viewport_set_scenario(viewport, find_world_3d()->get_scenario()); - } - - _update_listener(); -} - -Ref<World3D> Viewport::get_world_3d() const { - return world_3d; -} - Ref<World2D> Viewport::get_world_2d() const { return world_2d; } -Ref<World3D> Viewport::find_world_3d() const { - if (own_world_3d.is_valid()) { - return own_world_3d; - } else if (world_3d.is_valid()) { - return world_3d; - } else if (parent) { - return parent->find_world_3d(); - } else { - return Ref<World3D>(); - } -} - -Listener3D *Viewport::get_listener() const { - return listener; -} - -Camera3D *Viewport::get_camera() const { - return camera; -} - -void Viewport::enable_camera_override(bool p_enable) { -#ifndef _3D_DISABLED - if (p_enable == camera_override) { - return; - } - - if (p_enable) { - camera_override.rid = RenderingServer::get_singleton()->camera_create(); - } else { - RenderingServer::get_singleton()->free(camera_override.rid); - camera_override.rid = RID(); - } - - if (p_enable) { - RenderingServer::get_singleton()->viewport_attach_camera(viewport, camera_override.rid); - } else if (camera) { - RenderingServer::get_singleton()->viewport_attach_camera(viewport, camera->get_camera()); - } else { - RenderingServer::get_singleton()->viewport_attach_camera(viewport, RID()); - } -#endif -} - -bool Viewport::is_camera_override_enabled() const { - return camera_override; -} - -void Viewport::set_camera_override_transform(const Transform3D &p_transform) { - if (camera_override) { - camera_override.transform = p_transform; - RenderingServer::get_singleton()->camera_set_transform(camera_override.rid, p_transform); - } -} - -Transform3D Viewport::get_camera_override_transform() const { - if (camera_override) { - return camera_override.transform; - } - - return Transform3D(); -} - -void Viewport::set_camera_override_perspective(float p_fovy_degrees, float p_z_near, float p_z_far) { - if (camera_override) { - if (camera_override.fov == p_fovy_degrees && camera_override.z_near == p_z_near && - camera_override.z_far == p_z_far && camera_override.projection == CameraOverrideData::PROJECTION_PERSPECTIVE) { - return; - } - - camera_override.fov = p_fovy_degrees; - camera_override.z_near = p_z_near; - camera_override.z_far = p_z_far; - camera_override.projection = CameraOverrideData::PROJECTION_PERSPECTIVE; - - RenderingServer::get_singleton()->camera_set_perspective(camera_override.rid, camera_override.fov, camera_override.z_near, camera_override.z_far); - } -} - -void Viewport::set_camera_override_orthogonal(float p_size, float p_z_near, float p_z_far) { - if (camera_override) { - if (camera_override.size == p_size && camera_override.z_near == p_z_near && - camera_override.z_far == p_z_far && camera_override.projection == CameraOverrideData::PROJECTION_ORTHOGONAL) { - return; - } - - camera_override.size = p_size; - camera_override.z_near = p_z_near; - camera_override.z_far = p_z_far; - camera_override.projection = CameraOverrideData::PROJECTION_ORTHOGONAL; - - RenderingServer::get_singleton()->camera_set_orthogonal(camera_override.rid, camera_override.size, camera_override.z_near, camera_override.z_far); - } +Camera2D *Viewport::get_camera_2d() const { + return camera_2d; } Transform2D Viewport::get_final_transform() const { @@ -1401,16 +1023,6 @@ void Viewport::_update_canvas_items(Node *p_node) { } } -void Viewport::set_use_xr(bool p_use_xr) { - use_xr = p_use_xr; - - RS::get_singleton()->viewport_set_use_xr(viewport, use_xr); -} - -bool Viewport::is_using_xr() { - return use_xr; -} - Ref<ViewportTexture> Viewport::get_texture() const { return default_texture; } @@ -1493,7 +1105,10 @@ void Viewport::_gui_sort_roots() { void Viewport::_gui_cancel_tooltip() { gui.tooltip_control = nullptr; - gui.tooltip_timer = -1; + if (gui.tooltip_timer.is_valid()) { + gui.tooltip_timer->release_connections(); + gui.tooltip_timer = Ref<SceneTreeTimer>(); + } if (gui.tooltip_popup) { gui.tooltip_popup->queue_delete(); gui.tooltip_popup = nullptr; @@ -1508,6 +1123,12 @@ String Viewport::_gui_get_tooltip(Control *p_control, const Vector2 &p_pos, Cont while (p_control) { tooltip = p_control->get_tooltip(pos); + // Temporary solution for PopupMenus. + PopupMenu *menu = Object::cast_to<PopupMenu>(this); + if (menu) { + tooltip = menu->get_tooltip(pos); + } + if (r_tooltip_owner) { *r_tooltip_owner = p_control; } @@ -1562,6 +1183,9 @@ void Viewport::_gui_show_tooltip() { return; } + // Popup window which houses the tooltip content. + TooltipPanel *panel = memnew(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. Control *base_tooltip = tooltip_owner->make_custom_tooltip(tooltip_text); @@ -1569,13 +1193,14 @@ 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->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); - TooltipPanel *panel = memnew(TooltipPanel); panel->set_transient(false); panel->set_flag(Window::FLAG_NO_FOCUS, true); panel->set_wrap_controls(true); @@ -1612,11 +1237,9 @@ void Viewport::_gui_show_tooltip() { } void Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_input) { - //_block(); - Ref<InputEvent> ev = p_input; - //mouse wheel events can't be stopped + // Mouse wheel events can't be stopped. Ref<InputEventMouseButton> mb = p_input; bool cant_stop_me_now = (mb.is_valid() && @@ -1634,27 +1257,7 @@ void Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_inpu Control *control = Object::cast_to<Control>(ci); if (control) { if (control->data.mouse_filter != Control::MOUSE_FILTER_IGNORE) { - control->emit_signal(SceneStringNames::get_singleton()->gui_input, ev); //signal should be first, so it's possible to override an event (and then accept it) - } - if (gui.key_event_accepted) { - break; - } - if (!control->is_inside_tree()) { - break; - } - - if (control->data.mouse_filter != Control::MOUSE_FILTER_IGNORE) { - // Call both script and native methods. - Callable::CallError error; - Variant event = ev; - const Variant *args[1] = { &event }; - if (control->get_script_instance()) { - control->get_script_instance()->call(SceneStringNames::get_singleton()->_gui_input, args, 1, error); - } - MethodBind *method = ClassDB::get_method(control->get_class_name(), SceneStringNames::get_singleton()->_gui_input); - if (method) { - method->call(control, args, 1, error); - } + control->_call_gui_input(ev); } if (!control->is_inside_tree() || control->is_set_as_top_level()) { @@ -1672,11 +1275,9 @@ void Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_inpu break; } - ev = ev->xformed_by(ci->get_transform()); //transform event upwards + ev = ev->xformed_by(ci->get_transform()); // Transform event upwards. ci = ci->get_parent_item(); } - - //_unblock(); } void Viewport::_gui_call_notification(Control *p_control, int p_what) { @@ -1706,12 +1307,10 @@ void Viewport::_gui_call_notification(Control *p_control, int p_what) { ci = ci->get_parent_item(); } - - //_unblock(); } -Control *Viewport::_gui_find_control(const Point2 &p_global) { - //aca va subwindows +Control *Viewport::gui_find_control(const Point2 &p_global) { + // Handle subwindows. _gui_sort_roots(); for (List<Control *>::Element *E = gui.roots.back(); E; E = E->prev()) { @@ -1743,8 +1342,7 @@ Control *Viewport::_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_ } if (!p_node->is_visible()) { - //return _find_next_visible_control_at_pos(p_node,p_global,r_inv_xform); - return nullptr; //canvas item hidden, discard + return nullptr; // Canvas item hidden, discard. } Transform2D matrix = p_xform * p_node->get_transform(); @@ -1788,32 +1386,31 @@ Control *Viewport::_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_ } bool Viewport::_gui_drop(Control *p_at_control, Point2 p_at_pos, bool p_just_check) { - { //attempt grab, try parent controls too - CanvasItem *ci = p_at_control; - while (ci) { - Control *control = Object::cast_to<Control>(ci); - if (control) { - if (control->can_drop_data(p_at_pos, gui.drag_data)) { - if (!p_just_check) { - control->drop_data(p_at_pos, gui.drag_data); - } - - return true; + // Attempt grab, try parent controls too. + CanvasItem *ci = p_at_control; + while (ci) { + Control *control = Object::cast_to<Control>(ci); + if (control) { + if (control->can_drop_data(p_at_pos, gui.drag_data)) { + if (!p_just_check) { + control->drop_data(p_at_pos, gui.drag_data); } - if (control->data.mouse_filter == Control::MOUSE_FILTER_STOP) { - break; - } + return true; } - p_at_pos = ci->get_transform().xform(p_at_pos); - - if (ci->is_set_as_top_level()) { + if (control->data.mouse_filter == Control::MOUSE_FILTER_STOP) { break; } + } + + p_at_pos = ci->get_transform().xform(p_at_pos); - ci = ci->get_parent_item(); + if (ci->is_set_as_top_level()) { + break; } + + ci = ci->get_parent_item(); } return false; @@ -1833,24 +1430,10 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (mb->is_pressed()) { Size2 pos = mpos; if (gui.mouse_focus_mask) { - //do not steal mouse focus and stuff while a focus mask exists - gui.mouse_focus_mask |= 1 << (mb->get_button_index() - 1); //add the button to the mask + // Do not steal mouse focus and stuff while a focus mask exists. + gui.mouse_focus_mask |= 1 << (mb->get_button_index() - 1); // Add the button to the mask. } else { - bool is_handled = false; - - if (is_handled) { - set_input_as_handled(); - return; - } - - //Matrix32 parent_xform; - - /* - if (data.parent_canvas_item) - parent_xform=data.parent_canvas_item->get_global_transform(); - */ - - gui.mouse_focus = _gui_find_control(pos); + gui.mouse_focus = gui_find_control(pos); gui.last_mouse_focus = gui.mouse_focus; if (!gui.mouse_focus) { @@ -1866,7 +1449,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } } - mb = mb->xformed_by(Transform2D()); // make a copy of the event + mb = mb->xformed_by(Transform2D()); // Make a copy of the event. mb->set_global_position(pos); @@ -1883,7 +1466,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } #endif - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { //assign focus + if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { // Assign focus. CanvasItem *ci = gui.mouse_focus; while (ci) { Control *control = Object::cast_to<Control>(ci); @@ -1915,7 +1498,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { set_input_as_handled(); if (gui.drag_data.get_type() != Variant::NIL && mb->get_button_index() == MOUSE_BUTTON_LEFT) { - //alternate drop use (when using force_drag(), as proposed by #5342 + // Alternate drop use (when using force_drag(), as proposed by #5342). if (gui.mouse_focus) { _gui_drop(gui.mouse_focus, pos, false); } @@ -1929,7 +1512,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { gui.drag_preview_id = ObjectID(); } _propagate_viewport_notification(this, NOTIFICATION_DRAG_END); - //change mouse accordingly + // Change mouse accordingly. } _gui_cancel_tooltip(); @@ -1949,26 +1532,28 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { gui.dragging = false; gui.drag_mouse_over = nullptr; _propagate_viewport_notification(this, NOTIFICATION_DRAG_END); - //change mouse accordingly + // Change mouse accordingly. } - gui.mouse_focus_mask &= ~(1 << (mb->get_button_index() - 1)); //remove from mask + gui.mouse_focus_mask &= ~(1 << (mb->get_button_index() - 1)); // Remove from mask. if (!gui.mouse_focus) { - //release event is only sent if a mouse focus (previously pressed button) exists + // Release event is only sent if a mouse focus (previously pressed button) exists. return; } Size2 pos = mpos; - mb = mb->xformed_by(Transform2D()); //make a copy + mb = mb->xformed_by(Transform2D()); // Make a copy. mb->set_global_position(pos); pos = gui.focus_inv_xform.xform(pos); mb->set_position(pos); Control *mouse_focus = gui.mouse_focus; - //disable mouse focus if needed before calling input, this makes popups on mouse press event work better, as the release will never be received otherwise + // Disable mouse focus if needed before calling input, + // this makes popups on mouse press event work better, + // as the release will never be received otherwise. if (gui.mouse_focus_mask == 0) { gui.mouse_focus = nullptr; gui.forced_mouse_focus = false; @@ -1978,11 +1563,6 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { _gui_call_input(mouse_focus, mb); } - /*if (gui.drag_data.get_type()!=Variant::NIL && mb->get_button_index()==MOUSE_BUTTON_LEFT) { - _propagate_viewport_notification(this,NOTIFICATION_DRAG_END); - gui.drag_data=Variant(); //always clear - }*/ - // In case the mouse was released after for example dragging a scrollbar, // check whether the current control is different from the stored one. If // it is different, rather than wait for it to be updated the next time the @@ -1991,7 +1571,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (gui.mouse_focus) { over = gui.mouse_focus; } else { - over = _gui_find_control(mpos); + over = gui_find_control(mpos); } if (gui.mouse_focus_mask == 0 && over != gui.mouse_over) { @@ -2021,12 +1601,12 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Control *over = nullptr; - // D&D + // Drag & drop. if (!gui.drag_attempted && gui.mouse_focus && mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) { gui.drag_accum += mm->get_relative(); float len = gui.drag_accum.length(); if (len > 10) { - { //attempt grab, try parent controls too + { // Attempt grab, try parent controls too. CanvasItem *ci = gui.mouse_focus; while (ci) { Control *control = Object::cast_to<Control>(ci); @@ -2074,7 +1654,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (gui.mouse_focus) { over = gui.mouse_focus; } else { - over = _gui_find_control(mpos); + over = gui_find_control(mpos); } if (over != gui.mouse_over) { @@ -2099,14 +1679,14 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Vector2 speed = localizer.basis_xform(mm->get_speed()); Vector2 rel = localizer.basis_xform(mm->get_relative()); - mm = mm->xformed_by(Transform2D()); //make a copy + mm = mm->xformed_by(Transform2D()); // Make a copy. mm->set_global_position(mpos); mm->set_speed(speed); mm->set_relative(rel); if (mm->get_button_mask() == 0) { - //nothing pressed + // Nothing pressed. bool can_tooltip = true; @@ -2130,7 +1710,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { is_tooltip_shown = true; } } else { - is_tooltip_shown = true; //well, nothing to compare against, likely using custom control, so if it changes there is nothing we can do + is_tooltip_shown = true; // Nothing to compare against, likely using custom control, so if it changes there is nothing we can do. } } } else { @@ -2139,9 +1719,15 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } if (can_tooltip && !is_tooltip_shown) { + if (gui.tooltip_timer.is_valid()) { + gui.tooltip_timer->release_connections(); + gui.tooltip_timer = Ref<SceneTreeTimer>(); + } gui.tooltip_control = over; gui.tooltip_pos = over->get_screen_transform().xform(pos); - gui.tooltip_timer = gui.tooltip_delay; + gui.tooltip_timer = get_tree()->create_timer(gui.tooltip_delay); + gui.tooltip_timer->set_ignore_time_scale(true); + gui.tooltip_timer->connect("timeout", callable_mp(this, &Viewport::_gui_show_tooltip)); } } @@ -2181,7 +1767,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } if (gui.drag_data.get_type() != Variant::NIL) { - //handle dragandrop + // Handle drag & drop. Control *drag_preview = _gui_get_drag_preview(); if (drag_preview) { @@ -2191,9 +1777,8 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { gui.drag_mouse_over = over; gui.drag_mouse_over_pos = Vector2(); - //find the window this is above of - - //see if there is an embedder + // Find the window this is above of. + // See if there is an embedder. Viewport *embedder = nullptr; Vector2 viewport_pos; @@ -2201,7 +1786,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { embedder = this; viewport_pos = mpos; } else { - //not an embedder, but may be a subwindow of an embedder + // Not an embedder, but may be a subwindow of an embedder. Window *w = Object::cast_to<Window>(this); if (w) { if (w->is_embedded()) { @@ -2209,7 +1794,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Transform2D ai = (get_final_transform().affine_inverse() * _get_input_pre_xform()).affine_inverse(); - viewport_pos = ai.xform(mpos) + w->get_position(); //to parent coords + viewport_pos = ai.xform(mpos) + w->get_position(); // To parent coords. } } } @@ -2217,13 +1802,13 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Viewport *viewport_under = nullptr; if (embedder) { - //use embedder logic + // Use embedder logic. for (int i = embedder->gui.sub_windows.size() - 1; i >= 0; i--) { Window *sw = embedder->gui.sub_windows[i].window; Rect2 swrect = Rect2i(sw->get_position(), sw->get_size()); if (!sw->get_flag(Window::FLAG_BORDERLESS)) { - int title_height = sw->get_theme_constant("title_height"); + int title_height = sw->get_theme_constant(SNAME("title_height")); swrect.position.y -= title_height; swrect.size.y += title_height; } @@ -2235,11 +1820,11 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } if (!viewport_under) { - //not in a subwindow, likely in embedder + // Not in a subwindow, likely in embedder. viewport_under = embedder; } } else { - //use displayserver logic + // Use DisplayServer logic. Vector2i screen_mouse_pos = DisplayServer::get_singleton()->mouse_get_position(); DisplayServer::WindowID window_id = DisplayServer::get_singleton()->get_window_at_screen_position(screen_mouse_pos); @@ -2247,7 +1832,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (window_id != DisplayServer::INVALID_WINDOW_ID) { ObjectID object_under = DisplayServer::get_singleton()->window_get_attached_instance_id(window_id); - if (object_under != ObjectID()) { //fetch window + if (object_under != ObjectID()) { // Fetch window. Window *w = Object::cast_to<Window>(ObjectDB::get_instance(object_under)); if (w) { viewport_under = w; @@ -2260,8 +1845,8 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (viewport_under) { Transform2D ai = (viewport_under->get_final_transform().affine_inverse() * viewport_under->_get_input_pre_xform()); viewport_pos = ai.xform(viewport_pos); - //find control under at pos - gui.drag_mouse_over = viewport_under->_gui_find_control(viewport_pos); + // Find control under at position. + gui.drag_mouse_over = viewport_under->gui_find_control(viewport_pos); if (gui.drag_mouse_over) { Transform2D localizer = gui.drag_mouse_over->get_global_transform_with_canvas().affine_inverse(); gui.drag_mouse_over_pos = localizer.xform(viewport_pos); @@ -2289,10 +1874,10 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (touch_event.is_valid()) { Size2 pos = touch_event->get_position(); if (touch_event->is_pressed()) { - Control *over = _gui_find_control(pos); + Control *over = gui_find_control(pos); if (over) { if (over->can_process()) { - touch_event = touch_event->xformed_by(Transform2D()); //make a copy + touch_event = touch_event->xformed_by(Transform2D()); // Make a copy. if (over == gui.mouse_focus) { pos = gui.focus_inv_xform.xform(pos); } else { @@ -2306,7 +1891,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } } else if (touch_event->get_index() == 0 && gui.last_mouse_focus) { if (gui.last_mouse_focus->can_process()) { - touch_event = touch_event->xformed_by(Transform2D()); //make a copy + 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); @@ -2324,10 +1909,10 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Size2 pos = gesture_event->get_position(); - Control *over = _gui_find_control(pos); + Control *over = gui_find_control(pos); if (over) { if (over->can_process()) { - gesture_event = gesture_event->xformed_by(Transform2D()); //make a copy + gesture_event = gesture_event->xformed_by(Transform2D()); // Make a copy. if (over == gui.mouse_focus) { pos = gui.focus_inv_xform.xform(pos); } else { @@ -2345,7 +1930,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (drag_event.is_valid()) { Control *over = gui.mouse_focus; if (!over) { - over = _gui_find_control(drag_event->get_position()); + over = gui_find_control(drag_event->get_position()); } if (over) { if (over->can_process()) { @@ -2354,7 +1939,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Vector2 speed = localizer.basis_xform(drag_event->get_speed()); Vector2 rel = localizer.basis_xform(drag_event->get_relative()); - drag_event = drag_event->xformed_by(Transform2D()); //make a copy + drag_event = drag_event->xformed_by(Transform2D()); // Make a copy. drag_event->set_speed(speed); drag_event->set_relative(rel); @@ -2376,10 +1961,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (gui.key_focus) { gui.key_event_accepted = false; if (gui.key_focus->can_process()) { - gui.key_focus->call(SceneStringNames::get_singleton()->_gui_input, p_event); - if (gui.key_focus) { //maybe lost it - gui.key_focus->emit_signal(SceneStringNames::get_singleton()->gui_input, p_event); - } + gui.key_focus->_call_gui_input(p_event); } if (gui.key_event_accepted) { @@ -2388,12 +1970,11 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } } - Control *from = gui.key_focus ? gui.key_focus : nullptr; //hmm + Control *from = gui.key_focus ? gui.key_focus : nullptr; - //keyboard focus - //if (from && p_event->is_pressed() && !p_event->is_alt_pressed() && !p_event->is_meta_pressed() && !p_event->key->is_command_pressed()) { + // Keyboard focus. Ref<InputEventKey> k = p_event; - //need to check for mods, otherwise any combination of alt/ctrl/shift+<up/down/left/right/etc> is handled here when it shouldn't be. + // Need to check for mods, otherwise any combination of alt/ctrl/shift+<up/down/left/right/etc> is handled here when it shouldn't be. bool mods = k.is_valid() && (k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_shift_pressed() || k->is_meta_pressed()); if (from && p_event->is_pressed()) { @@ -2464,7 +2045,7 @@ void Viewport::_gui_set_drag_preview(Control *p_base, Control *p_control) { } p_control->set_as_top_level(true); p_control->set_position(gui.last_mouse_pos); - p_base->get_root_parent_control()->add_child(p_control); //add as child of viewport + p_base->get_root_parent_control()->add_child(p_control); // Add as child of viewport. p_control->raise(); gui.drag_preview_id = p_control->get_instance_id(); @@ -2566,13 +2147,13 @@ bool Viewport::_gui_control_has_focus(const Control *p_control) { } void Viewport::_gui_control_grab_focus(Control *p_control) { - //no need for change if (gui.key_focus && gui.key_focus == 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()); gui.key_focus = p_control; - emit_signal("gui_focus_changed", p_control); + emit_signal(SNAME("gui_focus_changed"), p_control); p_control->notification(Control::NOTIFICATION_FOCUS_ENTER); p_control->update(); } @@ -2599,7 +2180,7 @@ void Viewport::_drop_mouse_focus() { mb->set_global_position(c->get_local_mouse_position()); mb->set_button_index(MouseButton(i + 1)); mb->set_pressed(false); - c->call(SceneStringNames::get_singleton()->_gui_input, mb); + c->_call_gui_input(mb); } } } @@ -2607,20 +2188,41 @@ void Viewport::_drop_mouse_focus() { void Viewport::_drop_physics_mouseover(bool p_paused_only) { physics_has_last_mousepos = false; + _cleanup_mouseover_colliders(true, p_paused_only); + +#ifndef _3D_DISABLED + if (physics_object_over.is_valid()) { + CollisionObject3D *co = Object::cast_to<CollisionObject3D>(ObjectDB::get_instance(physics_object_over)); + if (co) { + if (!(p_paused_only && co->can_process())) { + co->_mouse_exit(); + physics_object_over = ObjectID(); + physics_object_capture = ObjectID(); + } + } + } +#endif // _3D_DISABLED +} + +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; 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) { + continue; + } + Object *o = ObjectDB::get_instance(E->key()); if (o) { CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o); if (co) { - if (p_paused_only && co->can_process()) { + if (p_clean_all_frames && p_paused_only && co->can_process()) { continue; } co->_mouse_exit(); - to_erase.push_back(E); } } + to_erase.push_back(E); } while (to_erase.size()) { @@ -2628,18 +2230,31 @@ void Viewport::_drop_physics_mouseover(bool p_paused_only) { to_erase.pop_front(); } -#ifndef _3D_DISABLED - if (physics_object_over.is_valid()) { - CollisionObject3D *co = Object::cast_to<CollisionObject3D>(ObjectDB::get_instance(physics_object_over)); - if (co) { - if (!(p_paused_only && co->can_process())) { - co->_mouse_exit(); - physics_object_over = ObjectID(); - physics_object_capture = ObjectID(); + // Per-shape. + List<Map<Pair<ObjectID, int>, uint64_t, PairSort<ObjectID, int>>::Element *> 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) { + continue; + } + + Object *o = ObjectDB::get_instance(E->key().first); + if (o) { + CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o); + if (co) { + if (p_clean_all_frames && p_paused_only && co->can_process()) { + continue; + } + co->_mouse_shape_exit(E->key().second); } } + shapes_to_erase.push_back(E); + } + + while (shapes_to_erase.size()) { + physics_2d_shape_mouseover.erase(shapes_to_erase.front()->get()); + shapes_to_erase.pop_front(); } -#endif } Control *Viewport::_gui_get_focus_owner() { @@ -2648,13 +2263,13 @@ Control *Viewport::_gui_get_focus_owner() { void Viewport::_gui_grab_click_focus(Control *p_control) { gui.mouse_click_grabber = p_control; - call_deferred("_post_gui_grab_click_focus"); + call_deferred(SNAME("_post_gui_grab_click_focus")); } void Viewport::_post_gui_grab_click_focus() { Control *focus_grabber = gui.mouse_click_grabber; if (!focus_grabber) { - // Redundant grab requests were made + // Redundant grab requests were made. return; } gui.mouse_click_grabber = nullptr; @@ -2672,12 +2287,12 @@ void Viewport::_post_gui_grab_click_focus() { Ref<InputEventMouseButton> mb; mb.instantiate(); - //send unclick + // Send unclick. mb->set_position(click); mb->set_button_index(MouseButton(i + 1)); mb->set_pressed(false); - gui.mouse_focus->call(SceneStringNames::get_singleton()->_gui_input, mb); + gui.mouse_focus->_call_gui_input(mb); } } @@ -2690,12 +2305,12 @@ void Viewport::_post_gui_grab_click_focus() { Ref<InputEventMouseButton> mb; mb.instantiate(); - //send click + // Send click. mb->set_position(click); mb->set_button_index(MouseButton(i + 1)); mb->set_pressed(true); - gui.mouse_focus->call_deferred(SceneStringNames::get_singleton()->_gui_input, mb); + MessageQueue::get_singleton()->push_callable(callable_mp(gui.mouse_focus, &Control::_call_gui_input), mb); } } } @@ -2703,9 +2318,9 @@ void Viewport::_post_gui_grab_click_focus() { /////////////////////////////// -void Viewport::input_text(const String &p_text) { +void Viewport::push_text_input(const String &p_text) { if (gui.subwindow_focused) { - gui.subwindow_focused->input_text(p_text); + gui.subwindow_focused->push_text_input(p_text); return; } @@ -2721,19 +2336,19 @@ Viewport::SubWindowResize Viewport::_sub_window_get_resize_margin(Window *p_subw Rect2i r = Rect2i(p_subwindow->get_position(), p_subwindow->get_size()); - int title_height = p_subwindow->get_theme_constant("title_height"); + int title_height = p_subwindow->get_theme_constant(SNAME("title_height")); r.position.y -= title_height; r.size.y += title_height; if (r.has_point(p_point)) { - return SUB_WINDOW_RESIZE_DISABLED; //it's inside, so no resize + return SUB_WINDOW_RESIZE_DISABLED; // It's inside, so no resize. } int dist_x = p_point.x < r.position.x ? (p_point.x - r.position.x) : (p_point.x > (r.position.x + r.size.x) ? (p_point.x - (r.position.x + r.size.x)) : 0); int dist_y = p_point.y < r.position.y ? (p_point.y - r.position.y) : (p_point.y > (r.position.y + r.size.y) ? (p_point.y - (r.position.y + r.size.y)) : 0); - int limit = p_subwindow->get_theme_constant("resize_margin"); + int limit = p_subwindow->get_theme_constant(SNAME("resize_margin")); if (ABS(dist_x) > limit) { return SUB_WINDOW_RESIZE_DISABLED; @@ -2786,12 +2401,12 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { if (gui.subwindow_drag == SUB_WINDOW_DRAG_CLOSE) { if (gui.subwindow_drag_close_rect.has_point(mb->get_position())) { - //close window + // Close window. gui.subwindow_focused->_event_callback(DisplayServer::WINDOW_EVENT_CLOSE_REQUEST); } } gui.subwindow_drag = SUB_WINDOW_DRAG_DISABLED; - if (gui.subwindow_focused != nullptr) { //may have been erased + if (gui.subwindow_focused != nullptr) { // May have been erased. _sub_window_update(gui.subwindow_focused); } } @@ -2815,7 +2430,7 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { new_rect.position.x = 0; } - int title_height = gui.subwindow_focused->get_flag(Window::FLAG_BORDERLESS) ? 0 : gui.subwindow_focused->get_theme_constant("title_height"); + int title_height = gui.subwindow_focused->get_flag(Window::FLAG_BORDERLESS) ? 0 : gui.subwindow_focused->get_theme_constant(SNAME("title_height")); if (new_rect.position.y < title_height) { new_rect.position.y = title_height; @@ -2898,28 +2513,28 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { gui.subwindow_focused->_rect_changed_callback(r); } - if (gui.subwindow_focused) { //may have been erased + if (gui.subwindow_focused) { // May have been erased. _sub_window_update(gui.subwindow_focused); } } - return true; //handled + return true; // Handled. } Ref<InputEventMouseButton> mb = p_event; - //if the event is a mouse button, we need to check whether another window was clicked + // If the event is a mouse button, we need to check whether another window was clicked. if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { bool click_on_window = false; for (int i = gui.sub_windows.size() - 1; i >= 0; i--) { SubWindow &sw = gui.sub_windows.write[i]; - //clicked inside window? + // Clicked inside window? Rect2i r = Rect2i(sw.window->get_position(), sw.window->get_size()); if (!sw.window->get_flag(Window::FLAG_BORDERLESS)) { - //check top bar - int title_height = sw.window->get_theme_constant("title_height"); + // Check top bar. + int title_height = sw.window->get_theme_constant(SNAME("title_height")); Rect2i title_bar = r; title_bar.position.y -= title_height; title_bar.size.y = title_height; @@ -2927,22 +2542,22 @@ 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("close_h_ofs"); - int close_v_ofs = sw.window->get_theme_constant("close_v_ofs"); - Ref<Texture2D> close_icon = sw.window->get_theme_icon("close"); + 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")); + Ref<Texture2D> close_icon = sw.window->get_theme_icon(SNAME("close")); Rect2 close_rect; close_rect.position = Vector2(r.position.x + r.size.x - close_v_ofs, r.position.y - close_h_ofs); close_rect.size = close_icon->get_size(); if (gui.subwindow_focused != sw.window) { - //refocus + // Refocus. _sub_window_grab_focus(sw.window); } if (close_rect.has_point(mb->get_position())) { gui.subwindow_drag = SUB_WINDOW_DRAG_CLOSE; - gui.subwindow_drag_close_inside = true; //starts inside + gui.subwindow_drag_close_inside = true; // Starts inside. gui.subwindow_drag_close_rect = close_rect; } else { gui.subwindow_drag = SUB_WINDOW_DRAG_MOVE; @@ -2963,9 +2578,9 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { } } if (!click_on_window && r.has_point(mb->get_position())) { - //clicked, see if it needs to fetch focus + // Clicked, see if it needs to fetch focus. if (gui.subwindow_focused != sw.window) { - //refocus + // Refocus. _sub_window_grab_focus(sw.window); } @@ -2978,7 +2593,7 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { } if (!click_on_window && gui.subwindow_focused) { - //no window found and clicked, remove focus + // No window found and clicked, remove focus. _sub_window_grab_focus(nullptr); } } @@ -3002,13 +2617,13 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { DisplayServer::get_singleton()->cursor_set_shape(shapes[resize]); - return true; //reserved for showing the resize cursor + return true; // Reserved for showing the resize cursor. } } } if (gui.subwindow_drag != SUB_WINDOW_DRAG_DISABLED) { - return true; // dragging, don't pass the event + return true; // Dragging, don't pass the event. } if (!gui.subwindow_focused) { @@ -3025,7 +2640,7 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { return true; } -void Viewport::input(const Ref<InputEvent> &p_event, bool p_local_coords) { +void Viewport::push_input(const Ref<InputEvent> &p_event, bool p_local_coords) { ERR_FAIL_COND(!is_inside_tree()); if (disable_input) { @@ -3055,7 +2670,7 @@ void Viewport::input(const Ref<InputEvent> &p_event, bool p_local_coords) { } if (!is_input_handled()) { - get_tree()->_call_input_pause(input_group, "_input", ev, this); //not a bug, must happen before GUI, order is _input -> gui input -> _unhandled input + get_tree()->_call_input_pause(input_group, SceneTree::CALL_INPUT_TYPE_INPUT, ev, this); //not a bug, must happen before GUI, order is _input -> gui input -> _unhandled input } if (!is_input_handled()) { @@ -3063,12 +2678,12 @@ void Viewport::input(const Ref<InputEvent> &p_event, bool p_local_coords) { } event_count++; - //get_tree()->call_group(SceneTree::GROUP_CALL_REVERSE|SceneTree::GROUP_CALL_REALTIME|SceneTree::GROUP_CALL_MULIILEVEL,gui_input_group,"_gui_input",ev); //special one for GUI, as controls use their own process check } -void Viewport::unhandled_input(const Ref<InputEvent> &p_event, bool p_local_coords) { +void Viewport::push_unhandled_input(const Ref<InputEvent> &p_event, bool p_local_coords) { ERR_FAIL_COND(p_event.is_null()); ERR_FAIL_COND(!is_inside_tree()); + local_input_handled = false; if (disable_input || !_can_consume_input_events()) { return; @@ -3085,12 +2700,12 @@ void Viewport::unhandled_input(const Ref<InputEvent> &p_event, bool p_local_coor ev = p_event; } - // Unhandled Input - get_tree()->_call_input_pause(unhandled_input_group, "_unhandled_input", ev, this); + // Unhandled Input. + get_tree()->_call_input_pause(unhandled_input_group, SceneTree::CALL_INPUT_TYPE_UNHANDLED_INPUT, ev, this); - // Unhandled key Input - used for performance reasons - This is called a lot less then _unhandled_input since it ignores MouseMotion, etc - if (!is_input_handled() && Object::cast_to<InputEventKey>(*ev) != nullptr) { - get_tree()->_call_input_pause(unhandled_key_input_group, "_unhandled_key_input", ev, this); + // Unhandled key Input - used for performance reasons - This is called a lot less than _unhandled_input since it ignores MouseMotion, etc. + if (!is_input_handled() && (Object::cast_to<InputEventKey>(*ev) != nullptr || Object::cast_to<InputEventShortcut>(*ev) != nullptr)) { + get_tree()->_call_input_pause(unhandled_key_input_group, SceneTree::CALL_INPUT_TYPE_UNHANDLED_KEY_INPUT, ev, this); } if (physics_object_picking && !is_input_handled()) { @@ -3099,7 +2714,7 @@ void Viewport::unhandled_input(const Ref<InputEvent> &p_event, bool p_local_coor Object::cast_to<InputEventMouseMotion>(*ev) || Object::cast_to<InputEventScreenDrag>(*ev) || Object::cast_to<InputEventScreenTouch>(*ev) || - Object::cast_to<InputEventKey>(*ev) //to remember state + Object::cast_to<InputEventKey>(*ev) // To remember state. )) { physics_picking_events.push_back(ev); @@ -3107,48 +2722,15 @@ void Viewport::unhandled_input(const Ref<InputEvent> &p_event, bool p_local_coor } } -void Viewport::set_use_own_world_3d(bool p_world_3d) { - if (p_world_3d == own_world_3d.is_valid()) { - return; - } - - if (is_inside_tree()) { - _propagate_exit_world(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 (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)); - } - } - - if (is_inside_tree()) { - _propagate_enter_world(this); - } - - if (is_inside_tree()) { - RenderingServer::get_singleton()->viewport_set_scenario(viewport, find_world_3d()->get_scenario()); - } - - _update_listener(); -} - -bool Viewport::is_using_own_world_3d() const { - return own_world_3d.is_valid(); -} - void Viewport::set_physics_object_picking(bool p_enable) { physics_object_picking = p_enable; - if (!physics_object_picking) { + if (physics_object_picking) { + add_to_group("_picking_viewports"); + } else { physics_picking_events.clear(); + if (is_in_group("_picking_viewports")) { + remove_from_group("_picking_viewports"); + } } } @@ -3178,10 +2760,6 @@ Variant Viewport::gui_get_drag_data() const { } TypedArray<String> Viewport::get_configuration_warnings() const { - /*if (get_parent() && !Object::cast_to<Control>(get_parent()) && !render_target) { - return TTR("This viewport is not set as render target. If you intend for it to display its contents directly to the screen, make it a child of a Control so it can obtain a size. Otherwise, make it a RenderTarget and assign its internal texture to some node for display."); - }*/ - TypedArray<String> warnings = Node::get_configuration_warnings(); if (size.x == 0 || size.y == 0) { @@ -3240,6 +2818,7 @@ void Viewport::set_lod_threshold(float p_pixels) { lod_threshold = p_pixels; RS::get_singleton()->viewport_set_lod_threshold(viewport, lod_threshold); } + float Viewport::get_lod_threshold() const { return lod_threshold; } @@ -3452,6 +3031,7 @@ void Viewport::set_sdf_oversize(SDFOversize p_sdf_oversize) { sdf_oversize = p_sdf_oversize; RS::get_singleton()->viewport_set_sdf_oversize_and_scale(viewport, RS::ViewportSDFOversize(sdf_oversize), RS::ViewportSDFScale(sdf_scale)); } + Viewport::SDFOversize Viewport::get_sdf_oversize() const { return sdf_oversize; } @@ -3461,17 +3041,429 @@ void Viewport::set_sdf_scale(SDFScale p_sdf_scale) { sdf_scale = p_sdf_scale; RS::get_singleton()->viewport_set_sdf_oversize_and_scale(viewport, RS::ViewportSDFOversize(sdf_oversize), RS::ViewportSDFScale(sdf_scale)); } + Viewport::SDFScale Viewport::get_sdf_scale() const { return sdf_scale; } +#ifndef _3D_DISABLED +Listener3D *Viewport::get_listener_3d() const { + return listener_3d; +} + +void Viewport::set_as_audio_listener_3d(bool p_enable) { + if (p_enable == audio_listener_3d) { + return; + } + + audio_listener_3d = p_enable; + _update_listener_3d(); +} + +bool Viewport::is_audio_listener_3d() const { + return audio_listener_3d; +} + +void Viewport::_update_listener_3d() { + if (AudioServer::get_singleton()) { + AudioServer::get_singleton()->notify_listener_changed(); + } +} + +void Viewport::_listener_transform_3d_changed_notify() { +} + +void Viewport::_listener_3d_set(Listener3D *p_listener) { + if (listener_3d == p_listener) { + return; + } + + listener_3d = p_listener; + + _update_listener_3d(); + _listener_transform_3d_changed_notify(); +} + +bool Viewport::_listener_3d_add(Listener3D *p_listener) { + listener_3d_set.insert(p_listener); + return listener_3d_set.size() == 1; +} + +void Viewport::_listener_3d_remove(Listener3D *p_listener) { + listener_3d_set.erase(p_listener); + if (listener_3d == p_listener) { + listener_3d = nullptr; + } +} + +void Viewport::_listener_3d_make_next_current(Listener3D *p_exclude) { + if (listener_3d_set.size() > 0) { + for (Set<Listener3D *>::Element *E = listener_3d_set.front(); E; E = E->next()) { + if (p_exclude == E->get()) { + continue; + } + if (!E->get()->is_inside_tree()) { + continue; + } + if (listener_3d != nullptr) { + return; + } + + E->get()->make_current(); + } + } else { + // Attempt to reset listener to the camera position. + if (camera_3d != nullptr) { + _update_listener_3d(); + _camera_3d_transform_changed_notify(); + } + } +} + +void Viewport::_collision_object_3d_input_event(CollisionObject3D *p_object, Camera3D *p_camera, const Ref<InputEvent> &p_input_event, const Vector3 &p_pos, const Vector3 &p_normal, int p_shape) { + Transform3D object_transform = p_object->get_global_transform(); + Transform3D camera_transform = p_camera->get_global_transform(); + ObjectID id = p_object->get_instance_id(); + + // Avoid sending the fake event unnecessarily if nothing really changed in the context. + if (object_transform == physics_last_object_transform && camera_transform == physics_last_camera_transform && physics_last_id == id) { + Ref<InputEventMouseMotion> mm = p_input_event; + if (mm.is_valid() && mm->get_device() == InputEvent::DEVICE_ID_INTERNAL) { + return; // Discarded. + } + } + p_object->_input_event_call(camera_3d, p_input_event, p_pos, p_normal, p_shape); + physics_last_object_transform = object_transform; + physics_last_camera_transform = camera_transform; + physics_last_id = id; +} + +Camera3D *Viewport::get_camera_3d() const { + return camera_3d; +} + +void Viewport::_camera_3d_transform_changed_notify() { +} + +void Viewport::_camera_3d_set(Camera3D *p_camera) { + if (camera_3d == p_camera) { + return; + } + + if (camera_3d) { + camera_3d->notification(Camera3D::NOTIFICATION_LOST_CURRENT); + } + + camera_3d = p_camera; + + if (!camera_3d_override) { + if (camera_3d) { + RenderingServer::get_singleton()->viewport_attach_camera(viewport, camera_3d->get_camera()); + } else { + RenderingServer::get_singleton()->viewport_attach_camera(viewport, RID()); + } + } + + if (camera_3d) { + camera_3d->notification(Camera3D::NOTIFICATION_BECAME_CURRENT); + } + + _update_listener_3d(); + _camera_3d_transform_changed_notify(); +} + +bool Viewport::_camera_3d_add(Camera3D *p_camera) { + camera_3d_set.insert(p_camera); + return camera_3d_set.size() == 1; +} + +void Viewport::_camera_3d_remove(Camera3D *p_camera) { + camera_3d_set.erase(p_camera); + if (camera_3d == p_camera) { + camera_3d->notification(Camera3D::NOTIFICATION_LOST_CURRENT); + camera_3d = nullptr; + } +} + +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()) { + continue; + } + if (!E->get()->is_inside_tree()) { + continue; + } + if (camera_3d != nullptr) { + return; + } + + E->get()->make_current(); + } +} + +void Viewport::enable_camera_3d_override(bool p_enable) { + if (p_enable == camera_3d_override) { + return; + } + + if (p_enable) { + camera_3d_override.rid = RenderingServer::get_singleton()->camera_create(); + } else { + RenderingServer::get_singleton()->free(camera_3d_override.rid); + camera_3d_override.rid = RID(); + } + + if (p_enable) { + RenderingServer::get_singleton()->viewport_attach_camera(viewport, camera_3d_override.rid); + } else if (camera_3d) { + RenderingServer::get_singleton()->viewport_attach_camera(viewport, camera_3d->get_camera()); + } else { + RenderingServer::get_singleton()->viewport_attach_camera(viewport, RID()); + } +} + +void Viewport::set_camera_3d_override_perspective(real_t p_fovy_degrees, real_t p_z_near, real_t p_z_far) { + if (camera_3d_override) { + if (camera_3d_override.fov == p_fovy_degrees && camera_3d_override.z_near == p_z_near && + camera_3d_override.z_far == p_z_far && camera_3d_override.projection == Camera3DOverrideData::PROJECTION_PERSPECTIVE) { + return; + } + + camera_3d_override.fov = p_fovy_degrees; + camera_3d_override.z_near = p_z_near; + camera_3d_override.z_far = p_z_far; + camera_3d_override.projection = Camera3DOverrideData::PROJECTION_PERSPECTIVE; + + RenderingServer::get_singleton()->camera_set_perspective(camera_3d_override.rid, camera_3d_override.fov, camera_3d_override.z_near, camera_3d_override.z_far); + } +} + +void Viewport::set_camera_3d_override_orthogonal(real_t p_size, real_t p_z_near, real_t p_z_far) { + if (camera_3d_override) { + if (camera_3d_override.size == p_size && camera_3d_override.z_near == p_z_near && + camera_3d_override.z_far == p_z_far && camera_3d_override.projection == Camera3DOverrideData::PROJECTION_ORTHOGONAL) { + return; + } + + camera_3d_override.size = p_size; + camera_3d_override.z_near = p_z_near; + camera_3d_override.z_far = p_z_far; + camera_3d_override.projection = Camera3DOverrideData::PROJECTION_ORTHOGONAL; + + RenderingServer::get_singleton()->camera_set_orthogonal(camera_3d_override.rid, camera_3d_override.size, camera_3d_override.z_near, camera_3d_override.z_far); + } +} + +void Viewport::set_disable_3d(bool p_disable) { + disable_3d = p_disable; + RenderingServer::get_singleton()->viewport_set_disable_3d(viewport, disable_3d); +} + +bool Viewport::is_3d_disabled() const { + return disable_3d; +} + +bool Viewport::is_camera_3d_override_enabled() const { + return camera_3d_override; +} + +void Viewport::set_camera_3d_override_transform(const Transform3D &p_transform) { + if (camera_3d_override) { + camera_3d_override.transform = p_transform; + RenderingServer::get_singleton()->camera_set_transform(camera_3d_override.rid, p_transform); + } +} + +Transform3D Viewport::get_camera_3d_override_transform() const { + if (camera_3d_override) { + return camera_3d_override.transform; + } + + return Transform3D(); +} + +Ref<World3D> Viewport::get_world_3d() const { + return world_3d; +} + +Ref<World3D> Viewport::find_world_3d() const { + if (own_world_3d.is_valid()) { + return own_world_3d; + } else if (world_3d.is_valid()) { + return world_3d; + } else if (parent) { + return parent->find_world_3d(); + } else { + return Ref<World3D>(); + } +} + +void Viewport::set_world_3d(const Ref<World3D> &p_world_3d) { + if (world_3d == p_world_3d) { + return; + } + + if (is_inside_tree()) { + _propagate_exit_world_3d(this); + } + + if (own_world_3d.is_valid() && world_3d.is_valid()) { + world_3d->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Viewport::_own_world_3d_changed)); + } + + world_3d = p_world_3d; + + if (own_world_3d.is_valid()) { + 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)); + } + } + + if (is_inside_tree()) { + _propagate_enter_world_3d(this); + } + + if (is_inside_tree()) { + RenderingServer::get_singleton()->viewport_set_scenario(viewport, find_world_3d()->get_scenario()); + } + + _update_listener_3d(); +} + +void Viewport::_own_world_3d_changed() { + ERR_FAIL_COND(world_3d.is_null()); + ERR_FAIL_COND(own_world_3d.is_null()); + + if (is_inside_tree()) { + _propagate_exit_world_3d(this); + } + + own_world_3d = world_3d->duplicate(); + + if (is_inside_tree()) { + _propagate_enter_world_3d(this); + } + + if (is_inside_tree()) { + RenderingServer::get_singleton()->viewport_set_scenario(viewport, find_world_3d()->get_scenario()); + } + + _update_listener_3d(); +} + +void Viewport::set_use_own_world_3d(bool p_world_3d) { + if (p_world_3d == own_world_3d.is_valid()) { + return; + } + + if (is_inside_tree()) { + _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 (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)); + } + } + + if (is_inside_tree()) { + _propagate_enter_world_3d(this); + } + + if (is_inside_tree()) { + RenderingServer::get_singleton()->viewport_set_scenario(viewport, find_world_3d()->get_scenario()); + } + + _update_listener_3d(); +} + +bool Viewport::is_using_own_world_3d() const { + return own_world_3d.is_valid(); +} + +void Viewport::_propagate_enter_world_3d(Node *p_node) { + if (p_node != this) { + if (!p_node->is_inside_tree()) { //may not have entered scene yet + return; + } + + if (Object::cast_to<Node3D>(p_node) || Object::cast_to<WorldEnvironment>(p_node)) { + p_node->notification(Node3D::NOTIFICATION_ENTER_WORLD); + } else { + Viewport *v = Object::cast_to<Viewport>(p_node); + if (v) { + if (v->world_3d.is_valid() || v->own_world_3d.is_valid()) { + return; + } + } + } + } + + for (int i = 0; i < p_node->get_child_count(); i++) { + _propagate_enter_world_3d(p_node->get_child(i)); + } +} + +void Viewport::_propagate_exit_world_3d(Node *p_node) { + if (p_node != this) { + if (!p_node->is_inside_tree()) { //may have exited scene already + return; + } + + if (Object::cast_to<Node3D>(p_node) || Object::cast_to<WorldEnvironment>(p_node)) { + p_node->notification(Node3D::NOTIFICATION_EXIT_WORLD); + } else { + Viewport *v = Object::cast_to<Viewport>(p_node); + if (v) { + if (v->world_3d.is_valid() || v->own_world_3d.is_valid()) { + return; + } + } + } + } + + for (int i = 0; i < p_node->get_child_count(); i++) { + _propagate_exit_world_3d(p_node->get_child(i)); + } +} + +void Viewport::set_use_xr(bool p_use_xr) { + use_xr = p_use_xr; + + RS::get_singleton()->viewport_set_use_xr(viewport, use_xr); +} + +bool Viewport::is_using_xr() { + return use_xr; +} + +void Viewport::set_scale_3d(const Scale3D p_scale_3d) { + scale_3d = p_scale_3d; + + RS::get_singleton()->viewport_set_scale_3d(viewport, RS::ViewportScale3D(scale_3d)); +} + +Viewport::Scale3D Viewport::get_scale_3d() const { + return scale_3d; +} + +#endif // _3D_DISABLED + void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("set_world_2d", "world_2d"), &Viewport::set_world_2d); ClassDB::bind_method(D_METHOD("get_world_2d"), &Viewport::get_world_2d); ClassDB::bind_method(D_METHOD("find_world_2d"), &Viewport::find_world_2d); - ClassDB::bind_method(D_METHOD("set_world_3d", "world_3d"), &Viewport::set_world_3d); - ClassDB::bind_method(D_METHOD("get_world_3d"), &Viewport::get_world_3d); - ClassDB::bind_method(D_METHOD("find_world_3d"), &Viewport::find_world_3d); ClassDB::bind_method(D_METHOD("set_canvas_transform", "xform"), &Viewport::set_canvas_transform); ClassDB::bind_method(D_METHOD("get_canvas_transform"), &Viewport::get_canvas_transform); @@ -3501,33 +3493,20 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("get_render_info", "type", "info"), &Viewport::get_render_info); - ClassDB::bind_method(D_METHOD("set_use_xr", "use"), &Viewport::set_use_xr); - ClassDB::bind_method(D_METHOD("is_using_xr"), &Viewport::is_using_xr); - ClassDB::bind_method(D_METHOD("get_texture"), &Viewport::get_texture); ClassDB::bind_method(D_METHOD("set_physics_object_picking", "enable"), &Viewport::set_physics_object_picking); ClassDB::bind_method(D_METHOD("get_physics_object_picking"), &Viewport::get_physics_object_picking); ClassDB::bind_method(D_METHOD("get_viewport_rid"), &Viewport::get_viewport_rid); - ClassDB::bind_method(D_METHOD("input_text", "text"), &Viewport::input_text); - ClassDB::bind_method(D_METHOD("input", "event", "in_local_coords"), &Viewport::input, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("unhandled_input", "event", "in_local_coords"), &Viewport::unhandled_input, DEFVAL(false)); - - ClassDB::bind_method(D_METHOD("set_use_own_world_3d", "enable"), &Viewport::set_use_own_world_3d); - ClassDB::bind_method(D_METHOD("is_using_own_world_3d"), &Viewport::is_using_own_world_3d); - - ClassDB::bind_method(D_METHOD("get_camera"), &Viewport::get_camera); - - ClassDB::bind_method(D_METHOD("set_as_audio_listener", "enable"), &Viewport::set_as_audio_listener); - ClassDB::bind_method(D_METHOD("is_audio_listener"), &Viewport::is_audio_listener); + ClassDB::bind_method(D_METHOD("push_text_input", "text"), &Viewport::push_text_input); + ClassDB::bind_method(D_METHOD("push_input", "event", "in_local_coords"), &Viewport::push_input, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("push_unhandled_input", "event", "in_local_coords"), &Viewport::push_unhandled_input, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_camera_2d"), &Viewport::get_camera_2d); ClassDB::bind_method(D_METHOD("set_as_audio_listener_2d", "enable"), &Viewport::set_as_audio_listener_2d); ClassDB::bind_method(D_METHOD("is_audio_listener_2d"), &Viewport::is_audio_listener_2d); - ClassDB::bind_method(D_METHOD("set_disable_3d", "disable"), &Viewport::set_disable_3d); - ClassDB::bind_method(D_METHOD("is_3d_disabled"), &Viewport::is_3d_disabled); - ClassDB::bind_method(D_METHOD("get_mouse_position"), &Viewport::get_mouse_position); ClassDB::bind_method(D_METHOD("warp_mouse", "to_position"), &Viewport::warp_mouse); @@ -3585,17 +3564,41 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("_process_picking"), &Viewport::_process_picking); +#ifndef _3D_DISABLED + ClassDB::bind_method(D_METHOD("set_world_3d", "world_3d"), &Viewport::set_world_3d); + ClassDB::bind_method(D_METHOD("get_world_3d"), &Viewport::get_world_3d); + ClassDB::bind_method(D_METHOD("find_world_3d"), &Viewport::find_world_3d); + + ClassDB::bind_method(D_METHOD("set_use_own_world_3d", "enable"), &Viewport::set_use_own_world_3d); + ClassDB::bind_method(D_METHOD("is_using_own_world_3d"), &Viewport::is_using_own_world_3d); + + ClassDB::bind_method(D_METHOD("get_camera_3d"), &Viewport::get_camera_3d); + ClassDB::bind_method(D_METHOD("set_as_audio_listener_3d", "enable"), &Viewport::set_as_audio_listener_3d); + ClassDB::bind_method(D_METHOD("is_audio_listener_3d"), &Viewport::is_audio_listener_3d); + + ClassDB::bind_method(D_METHOD("set_disable_3d", "disable"), &Viewport::set_disable_3d); + ClassDB::bind_method(D_METHOD("is_3d_disabled"), &Viewport::is_3d_disabled); + + ClassDB::bind_method(D_METHOD("set_use_xr", "use"), &Viewport::set_use_xr); + ClassDB::bind_method(D_METHOD("is_using_xr"), &Viewport::is_using_xr); + + ClassDB::bind_method(D_METHOD("set_scale_3d", "scale"), &Viewport::set_scale_3d); + ClassDB::bind_method(D_METHOD("get_scale_3d"), &Viewport::get_scale_3d); + + 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::INT, "scale_3d", PROPERTY_HINT_ENUM, String::utf8("Disabled,75%,50%,33%,25%")), "set_scale_3d", "get_scale_3d"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "audio_listener_enable_3d"), "set_as_audio_listener_3d", "is_audio_listener_3d"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "own_world_3d"), "set_use_own_world_3d", "is_using_own_world_3d"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "world_3d", PROPERTY_HINT_RESOURCE_TYPE, "World3D"), "set_world_3d", "get_world_3d"); +#endif // _3D_DISABLED ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "world_2d", PROPERTY_HINT_RESOURCE_TYPE, "World2D", PROPERTY_USAGE_NONE), "set_world_2d", "get_world_2d"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "transparent_bg"), "set_transparent_background", "has_transparent_background"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "handle_input_locally"), "set_handle_input_locally", "is_handling_input_locally"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "snap_2d_transforms_to_pixel"), "set_snap_2d_transforms_to_pixel", "is_snap_2d_transforms_to_pixel_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "snap_2d_vertices_to_pixel"), "set_snap_2d_vertices_to_pixel", "is_snap_2d_vertices_to_pixel_enabled"); ADD_GROUP("Rendering", ""); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_3d"), "set_disable_3d", "is_3d_disabled"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "msaa", PROPERTY_HINT_ENUM, "Disabled (Fastest),2x (Fast),4x (Average),8x (Slow),16x (Slower)"), "set_msaa", "get_msaa"); + 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_debanding"), "set_use_debanding", "is_using_debanding"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_occlusion_culling"), "set_use_occlusion_culling", "is_using_occlusion_culling"); @@ -3606,7 +3609,6 @@ void Viewport::_bind_methods() { 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"); ADD_GROUP("Audio Listener", "audio_listener_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "audio_listener_enable_2d"), "set_as_audio_listener_2d", "is_audio_listener_2d"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "audio_listener_enable_3d"), "set_as_audio_listener", "is_audio_listener"); ADD_GROUP("Physics", "physics_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "physics_object_picking"), "set_physics_object_picking", "get_physics_object_picking"); ADD_GROUP("GUI", "gui_"); @@ -3629,6 +3631,12 @@ void Viewport::_bind_methods() { ADD_SIGNAL(MethodInfo("size_changed")); ADD_SIGNAL(MethodInfo("gui_focus_changed", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Control"))); + BIND_ENUM_CONSTANT(SCALE_3D_DISABLED); + BIND_ENUM_CONSTANT(SCALE_3D_75_PERCENT); + BIND_ENUM_CONSTANT(SCALE_3D_50_PERCENT); + BIND_ENUM_CONSTANT(SCALE_3D_33_PERCENT); + BIND_ENUM_CONSTANT(SCALE_3D_25_PERCENT); + BIND_ENUM_CONSTANT(SHADOW_ATLAS_QUADRANT_SUBDIV_DISABLED); BIND_ENUM_CONSTANT(SHADOW_ATLAS_QUADRANT_SUBDIV_1); BIND_ENUM_CONSTANT(SHADOW_ATLAS_QUADRANT_SUBDIV_4); @@ -3642,7 +3650,6 @@ void Viewport::_bind_methods() { BIND_ENUM_CONSTANT(MSAA_2X); BIND_ENUM_CONSTANT(MSAA_4X); BIND_ENUM_CONSTANT(MSAA_8X); - BIND_ENUM_CONSTANT(MSAA_16X); BIND_ENUM_CONSTANT(MSAA_MAX); BIND_ENUM_CONSTANT(SCREEN_SPACE_AA_DISABLED); @@ -3717,10 +3724,8 @@ Viewport::Viewport() { viewport_textures.insert(default_texture.ptr()); default_texture->proxy = RS::get_singleton()->texture_proxy_create(texture_rid); - //internal_listener_2d = SpatialSound2DServer::get_singleton()->listener_create(); - canvas_layers.insert(nullptr); // This eases picking code (interpreted as the canvas of the Viewport) + canvas_layers.insert(nullptr); // This eases picking code (interpreted as the canvas of the Viewport). - //clear=true; set_shadow_atlas_size(shadow_atlas_size); for (int i = 0; i < 4; i++) { @@ -3743,11 +3748,16 @@ Viewport::Viewport() { gui.tooltip_delay = GLOBAL_DEF("gui/timers/tooltip_delay_sec", 0.5); ProjectSettings::get_singleton()->set_custom_property_info("gui/timers/tooltip_delay_sec", PropertyInfo(Variant::FLOAT, "gui/timers/tooltip_delay_sec", PROPERTY_HINT_RANGE, "0,5,0.01,or_greater")); // No negative numbers - set_sdf_oversize(sdf_oversize); //set to server +#ifndef _3D_DISABLED + int scale = GLOBAL_GET("rendering/3d/viewport/scale"); + set_scale_3d((Scale3D)scale); +#endif // _3D_DISABLED + + set_sdf_oversize(sdf_oversize); // Set to server. } Viewport::~Viewport() { - //erase itself from viewport textures + // Erase itself from viewport textures. for (Set<ViewportTexture *>::Element *E = viewport_textures.front(); E; E = E->next()) { E->get()->vp = nullptr; } diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 0f3bb8707d..bfb52c4b98 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -31,24 +31,26 @@ #ifndef VIEWPORT_H #define VIEWPORT_H -#include "core/math/transform_2d.h" #include "scene/main/node.h" #include "scene/resources/texture.h" -#include "scene/resources/world_2d.h" -#include "servers/display_server.h" -#include "servers/rendering_server.h" +#ifndef _3D_DISABLED class Camera3D; -class Camera2D; +class CollisionObject3D; class Listener3D; -class Control; +class World3D; +#endif // _3D_DISABLED + +class Listener2D; +class Camera2D; class CanvasItem; class CanvasLayer; -class Panel; +class Control; class Label; -class Timer; +class SceneTreeTimer; class Viewport; -class CollisionObject3D; +class Window; +class World2D; class ViewportTexture : public Texture2D { GDCLASS(ViewportTexture, Texture2D); @@ -87,6 +89,14 @@ class Viewport : public Node { GDCLASS(Viewport, Node); public: + enum Scale3D { + SCALE_3D_DISABLED, + SCALE_3D_75_PERCENT, + SCALE_3D_50_PERCENT, + SCALE_3D_33_PERCENT, + SCALE_3D_25_PERCENT + }; + enum ShadowAtlasQuadrantSubdiv { SHADOW_ATLAS_QUADRANT_SUBDIV_DISABLED, SHADOW_ATLAS_QUADRANT_SUBDIV_1, @@ -103,7 +113,7 @@ public: MSAA_2X, MSAA_4X, MSAA_8X, - MSAA_16X, + // 16x MSAA is not supported due to its high cost and driver bugs. MSAA_MAX }; @@ -192,38 +202,14 @@ private: Viewport *parent = nullptr; - Listener3D *listener = nullptr; - Set<Listener3D *> listeners; - - struct CameraOverrideData { - Transform3D transform; - enum Projection { - PROJECTION_PERSPECTIVE, - PROJECTION_ORTHOGONAL - }; - Projection projection = Projection::PROJECTION_PERSPECTIVE; - float fov = 0.0; - float size = 0.0; - float z_near = 0.0; - float z_far = 0.0; - RID rid; - - operator bool() const { - return rid != RID(); - } - } camera_override; - - Camera3D *camera = nullptr; - Set<Camera3D *> cameras; + Listener2D *listener_2d = nullptr; + Camera2D *camera_2d = nullptr; Set<CanvasLayer *> canvas_layers; RID viewport; RID current_canvas; RID subwindow_canvas; - bool audio_listener = false; - RID internal_listener; - bool audio_listener_2d = false; RID internal_listener_2d; @@ -237,7 +223,6 @@ private: Size2i size = Size2i(512, 512); Size2i size_2d_override; bool size_allocated = false; - bool use_xr = false; RID contact_2d_debug; RID contact_3d_debug_multimesh; @@ -261,7 +246,7 @@ private: Transform3D physics_last_camera_transform; ObjectID physics_last_id; bool physics_has_last_mousepos = false; - Vector2 physics_last_mousepos = Vector2(Math_INF, Math_INF); + Vector2 physics_last_mousepos = Vector2(INFINITY, INFINITY); struct { bool alt = false; bool control = false; @@ -271,16 +256,17 @@ private: } physics_last_mouse_state; - void _collision_object_input_event(CollisionObject3D *p_object, Camera3D *p_camera, const Ref<InputEvent> &p_input_event, const Vector3 &p_pos, const Vector3 &p_normal, int p_shape); - bool handle_input_locally = true; bool local_input_handled = false; + // Collider to frame Map<ObjectID, uint64_t> physics_2d_mouseover; + // Collider & shape to frame + Map<Pair<ObjectID, int>, uint64_t, PairSort<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); Ref<World2D> world_2d; - Ref<World3D> world_3d; - Ref<World3D> own_world_3d; Rect2i to_screen_rect; StringName input_group; @@ -288,13 +274,10 @@ private: StringName unhandled_input_group; StringName unhandled_key_input_group; - void _update_listener(); void _update_listener_2d(); bool disable_3d = false; - void _propagate_enter_world(Node *p_node); - void _propagate_exit_world(Node *p_node); void _propagate_viewport_notification(Node *p_node, int p_what); void _update_global_transform(); @@ -366,8 +349,8 @@ private: bool drag_attempted = false; Variant drag_data; ObjectID drag_preview_id; - float tooltip_timer = -1.0; - float tooltip_delay = 0.0; + Ref<SceneTreeTimer> tooltip_timer; + double tooltip_delay = 0.0; Transform2D focus_inv_xform; bool roots_order_dirty = false; List<Control *> roots; @@ -397,7 +380,6 @@ private: void _gui_call_notification(Control *p_control, int p_what); void _gui_sort_roots(); - Control *_gui_find_control(const Point2 &p_global); Control *_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_global, const Transform2D &p_xform, Transform2D &r_inv_xform); void _gui_input_event(Ref<InputEvent> p_event); @@ -436,19 +418,12 @@ private: bool _gui_drop(Control *p_at_control, Point2 p_at_pos, bool p_just_check); - friend class Listener3D; - void _listener_transform_changed_notify(); - void _listener_set(Listener3D *p_listener); - bool _listener_add(Listener3D *p_listener); //true if first - void _listener_remove(Listener3D *p_listener); - void _listener_make_next_current(Listener3D *p_exclude); + friend class Listener2D; + void _listener_2d_set(Listener2D *p_listener); + void _listener_2d_remove(Listener2D *p_listener); - friend class Camera3D; - void _camera_transform_changed_notify(); - void _camera_set(Camera3D *p_camera); - bool _camera_add(Camera3D *p_camera); //true if first - void _camera_remove(Camera3D *p_camera); - void _camera_make_next_current(Camera3D *p_exclude); + friend class Camera2D; + void _camera_2d_set(Camera2D *p_camera_2d); friend class CanvasLayer; void _canvas_layer_add(CanvasLayer *p_canvas_layer); @@ -461,8 +436,6 @@ private: void _gui_set_root_order_dirty(); - void _own_world_3d_changed(); - friend class Window; void _sub_window_update_order(); @@ -490,37 +463,17 @@ protected: public: uint64_t get_processed_events_count() const { return event_count; } - Listener3D *get_listener() const; - Camera3D *get_camera() const; - - void enable_camera_override(bool p_enable); - bool is_camera_override_enabled() const; - - void set_camera_override_transform(const Transform3D &p_transform); - Transform3D get_camera_override_transform() const; - - void set_camera_override_perspective(float p_fovy_degrees, float p_z_near, float p_z_far); - void set_camera_override_orthogonal(float p_size, float p_z_near, float p_z_far); - - void set_as_audio_listener(bool p_enable); - bool is_audio_listener() const; - + Listener2D *get_listener_2d() const; + Camera2D *get_camera_2d() const; void set_as_audio_listener_2d(bool p_enable); bool is_audio_listener_2d() const; - void set_disable_3d(bool p_disable); - bool is_3d_disabled() const; - void update_canvas_items(); Rect2 get_visible_rect() const; RID get_viewport_rid() const; - void set_world_3d(const Ref<World3D> &p_world_3d); void set_world_2d(const Ref<World2D> &p_world_2d); - Ref<World3D> get_world_3d() const; - Ref<World3D> find_world_3d() const; - Ref<World2D> get_world_2d() const; Ref<World2D> find_world_2d() const; @@ -541,9 +494,6 @@ public: void set_transparent_background(bool p_enable); bool has_transparent_background() const; - void set_use_xr(bool p_use_xr); - bool is_using_xr(); - Ref<ViewportTexture> get_texture() const; void set_shadow_atlas_size(int p_size); @@ -573,12 +523,9 @@ public: Vector2 get_camera_coords(const Vector2 &p_viewport_coords) const; Vector2 get_camera_rect_size() const; - void set_use_own_world_3d(bool p_world_3d); - bool is_using_own_world_3d() const; - - void input_text(const String &p_text); - void input(const Ref<InputEvent> &p_event, bool p_local_coords = false); - void unhandled_input(const Ref<InputEvent> &p_event, bool p_local_coords = false); + void push_text_input(const String &p_text); + void push_input(const Ref<InputEvent> &p_event, bool p_local_coords = false); + void push_unhandled_input(const Ref<InputEvent> &p_event, bool p_local_coords = false); void set_disable_input(bool p_disable); bool is_input_disabled() const; @@ -618,6 +565,8 @@ public: bool gui_is_dragging() const; + Control *gui_find_control(const Point2 &p_global); + void set_sdf_oversize(SDFOversize p_sdf_oversize); SDFOversize get_sdf_oversize() const; @@ -641,6 +590,84 @@ public: void pass_mouse_focus_to(Viewport *p_viewport, Control *p_control); +#ifndef _3D_DISABLED + bool use_xr = false; + Scale3D scale_3d = SCALE_3D_DISABLED; + friend class Listener3D; + Listener3D *listener_3d = nullptr; + Set<Listener3D *> listener_3d_set; + bool audio_listener_3d = false; + RID internal_listener_3d; + Listener3D *get_listener_3d() const; + void set_as_audio_listener_3d(bool p_enable); + bool is_audio_listener_3d() const; + void _update_listener_3d(); + void _listener_transform_3d_changed_notify(); + void _listener_3d_set(Listener3D *p_listener); + bool _listener_3d_add(Listener3D *p_listener); //true if first + void _listener_3d_remove(Listener3D *p_listener); + void _listener_3d_make_next_current(Listener3D *p_exclude); + + void _collision_object_3d_input_event(CollisionObject3D *p_object, Camera3D *p_camera, const Ref<InputEvent> &p_input_event, const Vector3 &p_pos, const Vector3 &p_normal, int p_shape); + + struct Camera3DOverrideData { + Transform3D transform; + enum Projection { + PROJECTION_PERSPECTIVE, + PROJECTION_ORTHOGONAL + }; + Projection projection = Projection::PROJECTION_PERSPECTIVE; + real_t fov = 0.0; + real_t size = 0.0; + real_t z_near = 0.0; + real_t z_far = 0.0; + RID rid; + + operator bool() const { + return rid != RID(); + } + } camera_3d_override; + + friend class Camera3D; + Camera3D *camera_3d = nullptr; + Set<Camera3D *> camera_3d_set; + Camera3D *get_camera_3d() const; + void _camera_3d_transform_changed_notify(); + void _camera_3d_set(Camera3D *p_camera); + bool _camera_3d_add(Camera3D *p_camera); //true if first + void _camera_3d_remove(Camera3D *p_camera); + void _camera_3d_make_next_current(Camera3D *p_exclude); + + void enable_camera_3d_override(bool p_enable); + bool is_camera_3d_override_enabled() const; + + void set_camera_3d_override_transform(const Transform3D &p_transform); + Transform3D get_camera_3d_override_transform() const; + + void set_camera_3d_override_perspective(real_t p_fovy_degrees, real_t p_z_near, real_t p_z_far); + void set_camera_3d_override_orthogonal(real_t p_size, real_t p_z_near, real_t p_z_far); + + void set_disable_3d(bool p_disable); + bool is_3d_disabled() const; + + Ref<World3D> world_3d; + Ref<World3D> own_world_3d; + void set_world_3d(const Ref<World3D> &p_world_3d); + 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); + bool is_using_own_world_3d() const; + void _propagate_enter_world_3d(Node *p_node); + void _propagate_exit_world_3d(Node *p_node); + + void set_use_xr(bool p_use_xr); + bool is_using_xr(); + + void set_scale_3d(const Scale3D p_scale_3d); + Scale3D get_scale_3d() const; +#endif // _3D_DISABLED + Viewport(); ~Viewport(); }; @@ -697,6 +724,7 @@ VARIANT_ENUM_CAST(SubViewport::UpdateMode); VARIANT_ENUM_CAST(Viewport::ShadowAtlasQuadrantSubdiv); VARIANT_ENUM_CAST(Viewport::MSAA); VARIANT_ENUM_CAST(Viewport::ScreenSpaceAA); +VARIANT_ENUM_CAST(Viewport::Scale3D); VARIANT_ENUM_CAST(Viewport::DebugDraw); VARIANT_ENUM_CAST(Viewport::SDFScale); VARIANT_ENUM_CAST(Viewport::SDFOversize); diff --git a/scene/main/window.cpp b/scene/main/window.cpp index cf2a6b2adf..ca5a3915d0 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -31,10 +31,8 @@ #include "window.h" #include "core/debugger/engine_debugger.h" -#include "core/os/keyboard.h" #include "core/string/translation.h" #include "scene/gui/control.h" -#include "scene/resources/font.h" #include "scene/scene_string_names.h" void Window::set_title(const String &p_title) { @@ -42,9 +40,8 @@ void Window::set_title(const String &p_title) { if (embedder) { embedder->_sub_window_update(this); - } else if (window_id != DisplayServer::INVALID_WINDOW_ID) { - DisplayServer::get_singleton()->window_set_title(p_title, window_id); + DisplayServer::get_singleton()->window_set_title(atr(p_title), window_id); } } @@ -115,7 +112,7 @@ Size2i Window::get_max_size() const { void Window::set_min_size(const Size2i &p_min_size) { min_size = p_min_size; - if (window_id != DisplayServer::INVALID_WINDOW_ID) { + if (!wrap_controls && window_id != DisplayServer::INVALID_WINDOW_ID) { DisplayServer::get_singleton()->window_set_min_size(min_size, window_id); } _update_window_size(); @@ -227,12 +224,13 @@ void Window::_make_window() { } } - window_id = DisplayServer::get_singleton()->create_sub_window(DisplayServer::WindowMode(mode), f, Rect2i(position, size)); + DisplayServer::VSyncMode vsync_mode = DisplayServer::get_singleton()->window_get_vsync_mode(DisplayServer::MAIN_WINDOW_ID); + window_id = DisplayServer::get_singleton()->create_sub_window(DisplayServer::WindowMode(mode), vsync_mode, f, Rect2i(position, size)); ERR_FAIL_COND(window_id == DisplayServer::INVALID_WINDOW_ID); DisplayServer::get_singleton()->window_set_current_screen(current_screen, window_id); DisplayServer::get_singleton()->window_set_max_size(max_size, window_id); DisplayServer::get_singleton()->window_set_min_size(min_size, window_id); - DisplayServer::get_singleton()->window_set_title(tr(title), window_id); + DisplayServer::get_singleton()->window_set_title(atr(title), window_id); DisplayServer::get_singleton()->window_attach_instance_id(get_instance_id(), window_id); _update_window_size(); @@ -302,7 +300,7 @@ void Window::_propagate_window_notification(Node *p_node, int p_notification) { Node *child = p_node->get_child(i); Window *window = Object::cast_to<Window>(child); if (window) { - break; + continue; } _propagate_window_notification(child, p_notification); } @@ -312,39 +310,39 @@ void Window::_event_callback(DisplayServer::WindowEvent p_event) { switch (p_event) { case DisplayServer::WINDOW_EVENT_MOUSE_ENTER: { _propagate_window_notification(this, NOTIFICATION_WM_MOUSE_ENTER); - emit_signal("mouse_entered"); + emit_signal(SNAME("mouse_entered")); DisplayServer::get_singleton()->cursor_set_shape(DisplayServer::CURSOR_ARROW); //restore cursor shape } break; case DisplayServer::WINDOW_EVENT_MOUSE_EXIT: { _propagate_window_notification(this, NOTIFICATION_WM_MOUSE_EXIT); - emit_signal("mouse_exited"); + emit_signal(SNAME("mouse_exited")); } break; case DisplayServer::WINDOW_EVENT_FOCUS_IN: { focused = true; _propagate_window_notification(this, NOTIFICATION_WM_WINDOW_FOCUS_IN); - emit_signal("focus_entered"); + emit_signal(SNAME("focus_entered")); } break; case DisplayServer::WINDOW_EVENT_FOCUS_OUT: { focused = false; _propagate_window_notification(this, NOTIFICATION_WM_WINDOW_FOCUS_OUT); - emit_signal("focus_exited"); + emit_signal(SNAME("focus_exited")); } break; case DisplayServer::WINDOW_EVENT_CLOSE_REQUEST: { if (exclusive_child != nullptr) { break; //has an exclusive child, can't get events until child is closed } _propagate_window_notification(this, NOTIFICATION_WM_CLOSE_REQUEST); - emit_signal("close_requested"); + emit_signal(SNAME("close_requested")); } break; case DisplayServer::WINDOW_EVENT_GO_BACK_REQUEST: { _propagate_window_notification(this, NOTIFICATION_WM_GO_BACK_REQUEST); - emit_signal("go_back_requested"); + emit_signal(SNAME("go_back_requested")); } break; case DisplayServer::WINDOW_EVENT_DPI_CHANGE: { _update_viewport_size(); _propagate_window_notification(this, NOTIFICATION_WM_DPI_CHANGE); - emit_signal("dpi_changed"); + emit_signal(SNAME("dpi_changed")); } break; } } @@ -541,6 +539,7 @@ void Window::_update_window_size() { embedder->_sub_window_update(this); } 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); } //update the viewport @@ -660,8 +659,8 @@ void Window::_update_viewport_size() { if (!use_font_oversampling) { font_oversampling = 1.0; } - if (TS->font_get_oversampling() != font_oversampling) { - TS->font_set_oversampling(font_oversampling); + if (TS->font_get_global_oversampling() != font_oversampling) { + TS->font_set_global_oversampling(font_oversampling); } } @@ -698,93 +697,98 @@ Viewport *Window::_get_embedder() const { } void Window::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE) { - bool embedded = false; - { - embedder = _get_embedder(); - - if (embedder) { - embedded = true; - - if (!visible) { - embedder = nullptr; //not yet since not visible + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + bool embedded = false; + { + embedder = _get_embedder(); + + if (embedder) { + embedded = true; + + if (!visible) { + embedder = nullptr; //not yet since not visible + } } } - } - - if (embedded) { - //create as embedded - if (embedder) { - embedder->_sub_window_register(this); - RS::get_singleton()->viewport_set_update_mode(get_viewport_rid(), RS::VIEWPORT_UPDATE_WHEN_PARENT_VISIBLE); - _update_window_size(); - } - } else { - if (get_parent() == nullptr) { - //it's the root window! - visible = true; //always visible - window_id = DisplayServer::MAIN_WINDOW_ID; - DisplayServer::get_singleton()->window_attach_instance_id(get_instance_id(), window_id); - _update_from_window(); - //since this window already exists (created on start), we must update pos and size from it - { - position = DisplayServer::get_singleton()->window_get_position(window_id); - size = DisplayServer::get_singleton()->window_get_size(window_id); + if (embedded) { + //create as embedded + if (embedder) { + embedder->_sub_window_register(this); + RS::get_singleton()->viewport_set_update_mode(get_viewport_rid(), RS::VIEWPORT_UPDATE_WHEN_PARENT_VISIBLE); + _update_window_size(); } - _update_viewport_size(); //then feed back to the viewport - _update_window_callbacks(); - RS::get_singleton()->viewport_set_update_mode(get_viewport_rid(), RS::VIEWPORT_UPDATE_WHEN_VISIBLE); + } else { - //create - if (visible) { - _make_window(); + if (get_parent() == nullptr) { + //it's the root window! + visible = true; //always visible + window_id = DisplayServer::MAIN_WINDOW_ID; + DisplayServer::get_singleton()->window_attach_instance_id(get_instance_id(), window_id); + _update_from_window(); + //since this window already exists (created on start), we must update pos and size from it + { + position = DisplayServer::get_singleton()->window_get_position(window_id); + size = DisplayServer::get_singleton()->window_get_size(window_id); + } + _update_viewport_size(); //then feed back to the viewport + _update_window_callbacks(); + RS::get_singleton()->viewport_set_update_mode(get_viewport_rid(), RS::VIEWPORT_UPDATE_WHEN_VISIBLE); + } else { + //create + if (visible) { + _make_window(); + } } } - } - - if (transient) { - _make_transient(); - } - if (visible) { - notification(NOTIFICATION_VISIBILITY_CHANGED); - emit_signal(SceneStringNames::get_singleton()->visibility_changed); - RS::get_singleton()->viewport_set_active(get_viewport_rid(), true); - } - } - - if (p_what == NOTIFICATION_READY) { - if (wrap_controls) { - _update_child_controls(); - } - } - if (p_what == NOTIFICATION_TRANSLATION_CHANGED) { - child_controls_changed(); - } + if (transient) { + _make_transient(); + } + if (visible) { + notification(NOTIFICATION_VISIBILITY_CHANGED); + emit_signal(SceneStringNames::get_singleton()->visibility_changed); + RS::get_singleton()->viewport_set_active(get_viewport_rid(), true); + } + } break; + case NOTIFICATION_READY: { + if (wrap_controls) { + _update_child_controls(); + } + } break; + case NOTIFICATION_TRANSLATION_CHANGED: { + if (embedder) { + embedder->_sub_window_update(this); + } else if (window_id != DisplayServer::INVALID_WINDOW_ID) { + DisplayServer::get_singleton()->window_set_title(atr(title), window_id); + } - if (p_what == NOTIFICATION_EXIT_TREE) { - if (transient) { - _clear_transient(); - } + child_controls_changed(); + } break; + case NOTIFICATION_EXIT_TREE: { + if (transient) { + _clear_transient(); + } - if (!is_embedded() && window_id != DisplayServer::INVALID_WINDOW_ID) { - if (window_id == DisplayServer::MAIN_WINDOW_ID) { - RS::get_singleton()->viewport_set_update_mode(get_viewport_rid(), RS::VIEWPORT_UPDATE_DISABLED); - _update_window_callbacks(); + if (!is_embedded() && window_id != DisplayServer::INVALID_WINDOW_ID) { + if (window_id == DisplayServer::MAIN_WINDOW_ID) { + RS::get_singleton()->viewport_set_update_mode(get_viewport_rid(), RS::VIEWPORT_UPDATE_DISABLED); + _update_window_callbacks(); + } else { + _clear_window(); + } } else { - _clear_window(); - } - } else { - if (embedder) { - embedder->_sub_window_remove(this); - embedder = nullptr; - RS::get_singleton()->viewport_set_update_mode(get_viewport_rid(), RS::VIEWPORT_UPDATE_DISABLED); + if (embedder) { + embedder->_sub_window_remove(this); + embedder = nullptr; + RS::get_singleton()->viewport_set_update_mode(get_viewport_rid(), RS::VIEWPORT_UPDATE_DISABLED); + } + _update_viewport_size(); //called by clear and make, which does not happen here } - _update_viewport_size(); //called by clear and make, which does not happen here - } - RS::get_singleton()->viewport_set_active(get_viewport_rid(), false); + RS::get_singleton()->viewport_set_active(get_viewport_rid(), false); + } break; } } @@ -880,7 +884,7 @@ void Window::child_controls_changed() { } updating_child_controls = true; - call_deferred("_update_child_controls"); + call_deferred(SNAME("_update_child_controls")); } bool Window::_can_consume_input_events() const { @@ -912,18 +916,18 @@ void Window::_window_input(const Ref<InputEvent> &p_ev) { emit_signal(SceneStringNames::get_singleton()->window_input, p_ev); - input(p_ev); + push_input(p_ev); if (!is_input_handled()) { - unhandled_input(p_ev); + push_unhandled_input(p_ev); } } void Window::_window_input_text(const String &p_text) { - input_text(p_text); + push_text_input(p_text); } void Window::_window_drop_files(const Vector<String> &p_files) { - emit_signal("files_dropped", p_files, current_screen); + emit_signal(SNAME("files_dropped"), p_files, current_screen); } Viewport *Window::get_parent_viewport() const { @@ -1041,7 +1045,7 @@ void Window::popup_centered_ratio(float p_ratio) { } void Window::popup(const Rect2i &p_screen_rect) { - emit_signal("about_to_popup"); + emit_signal(SNAME("about_to_popup")); // Update window size to calculate the actual window size based on contents minimum size and minimum size. _update_window_size(); @@ -1169,23 +1173,24 @@ Ref<Theme> Window::get_theme() const { return theme; } -void Window::set_theme_custom_type(const StringName &p_theme_type) { - theme_custom_type = p_theme_type; +void Window::set_theme_type_variation(const StringName &p_theme_type) { + theme_type_variation = p_theme_type; Control::_propagate_theme_changed(this, theme_owner, theme_owner_window); } -StringName Window::get_theme_custom_type() const { - return theme_custom_type; +StringName Window::get_theme_type_variation() const { + return theme_type_variation; } void Window::_get_theme_type_dependencies(const StringName &p_theme_type, List<StringName> *p_list) const { - if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_custom_type) { - if (theme_custom_type != StringName()) { - p_list->push_back(theme_custom_type); + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) { + if (Theme::get_project_default().is_valid() && Theme::get_project_default()->get_type_variation_base(theme_type_variation) != StringName()) { + Theme::get_project_default()->get_type_dependencies(get_class_name(), theme_type_variation, p_list); + } else { + Theme::get_default()->get_type_dependencies(get_class_name(), theme_type_variation, p_list); } - Theme::get_type_dependencies(get_class_name(), p_list); } else { - Theme::get_type_dependencies(p_theme_type, p_list); + Theme::get_default()->get_type_dependencies(p_theme_type, StringName(), p_list); } } @@ -1322,14 +1327,14 @@ bool Window::is_layout_rtl() const { if (parent) { return parent->is_layout_rtl(); } else { - if (GLOBAL_GET("internationalization/rendering/force_right_to_left_layout_direction")) { + if (GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) { return true; } String locale = TranslationServer::get_singleton()->get_tool_locale(); return TS->is_locale_right_to_left(locale); } } else if (layout_dir == LAYOUT_DIRECTION_LOCALE) { - if (GLOBAL_GET("internationalization/rendering/force_right_to_left_layout_direction")) { + if (GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) { return true; } String locale = TranslationServer::get_singleton()->get_tool_locale(); @@ -1339,6 +1344,48 @@ bool Window::is_layout_rtl() const { } } +void Window::set_auto_translate(bool p_enable) { + if (p_enable == auto_translate) { + return; + } + + auto_translate = p_enable; + + notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); +} + +bool Window::is_auto_translating() const { + return auto_translate; +} + +void Window::_validate_property(PropertyInfo &property) const { + if (property.name == "theme_type_variation") { + List<StringName> names; + + // Only the default theme and the project theme are used for the list of options. + // This is an imposed limitation to simplify the logic needed to leverage those options. + Theme::get_default()->get_type_variation_list(get_class_name(), &names); + if (Theme::get_project_default().is_valid()) { + Theme::get_project_default()->get_type_variation_list(get_class_name(), &names); + } + names.sort_custom<StringName::AlphCompare>(); + + Vector<StringName> unique_names; + String hint_string; + for (const StringName &E : names) { + // Skip duplicate values. + if (unique_names.has(E)) { + continue; + } + + hint_string += String(E) + ","; + unique_names.append(E); + } + + property.hint_string = hint_string; + } +} + void Window::_bind_methods() { ClassDB::bind_method(D_METHOD("set_title", "title"), &Window::set_title); ClassDB::bind_method(D_METHOD("get_title"), &Window::get_title); @@ -1416,8 +1463,8 @@ void Window::_bind_methods() { ClassDB::bind_method(D_METHOD("set_theme", "theme"), &Window::set_theme); ClassDB::bind_method(D_METHOD("get_theme"), &Window::get_theme); - ClassDB::bind_method(D_METHOD("set_theme_custom_type", "theme_type"), &Window::set_theme_custom_type); - ClassDB::bind_method(D_METHOD("get_theme_custom_type"), &Window::get_theme_custom_type); + ClassDB::bind_method(D_METHOD("set_theme_type_variation", "theme_type"), &Window::set_theme_type_variation); + ClassDB::bind_method(D_METHOD("get_theme_type_variation"), &Window::get_theme_type_variation); ClassDB::bind_method(D_METHOD("get_theme_icon", "name", "theme_type"), &Window::get_theme_icon, DEFVAL("")); ClassDB::bind_method(D_METHOD("get_theme_stylebox", "name", "theme_type"), &Window::get_theme_stylebox, DEFVAL("")); @@ -1437,6 +1484,9 @@ void Window::_bind_methods() { ClassDB::bind_method(D_METHOD("get_layout_direction"), &Window::get_layout_direction); ClassDB::bind_method(D_METHOD("is_layout_rtl"), &Window::is_layout_rtl); + ClassDB::bind_method(D_METHOD("set_auto_translate", "enable"), &Window::set_auto_translate); + ClassDB::bind_method(D_METHOD("is_auto_translating"), &Window::is_auto_translating); + ClassDB::bind_method(D_METHOD("popup", "rect"), &Window::popup, DEFVAL(Rect2i())); ClassDB::bind_method(D_METHOD("popup_on_parent", "parent_rect"), &Window::popup_on_parent); ClassDB::bind_method(D_METHOD("popup_centered_ratio", "ratio"), &Window::popup_centered_ratio, DEFVAL(0.8)); @@ -1447,7 +1497,8 @@ void Window::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "position"), "set_position", "get_position"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "size"), "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::STRING, "current_screen"), "set_current_screen", "get_current_screen"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "current_screen"), "set_current_screen", "get_current_screen"); + ADD_GROUP("Flags", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "wrap_controls"), "set_wrap_controls", "is_wrapping_controls"); @@ -1458,16 +1509,22 @@ void Window::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "always_on_top"), "set_flag", "get_flag", FLAG_ALWAYS_ON_TOP); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "transparent"), "set_flag", "get_flag", FLAG_TRANSPARENT); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "unfocusable"), "set_flag", "get_flag", FLAG_NO_FOCUS); + 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_GROUP("Content Scale", "content_scale_"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "content_scale_size"), "set_content_scale_size", "get_content_scale_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "content_scale_mode", PROPERTY_HINT_ENUM, "Disabled,Canvas Items,Viewport"), "set_content_scale_mode", "get_content_scale_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "content_scale_aspect", PROPERTY_HINT_ENUM, "Ignore,Keep,Keep Width,Keep Height,Expand"), "set_content_scale_aspect", "get_content_scale_aspect"); + ADD_GROUP("Theme", "theme_"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "theme", PROPERTY_HINT_RESOURCE_TYPE, "Theme"), "set_theme", "get_theme"); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "theme_custom_type"), "set_theme_custom_type", "get_theme_custom_type"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "theme_type_variation", PROPERTY_HINT_ENUM_SUGGESTION), "set_theme_type_variation", "get_theme_type_variation"); + + ADD_GROUP("Auto Translate", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_translate"), "set_auto_translate", "is_auto_translating"); ADD_SIGNAL(MethodInfo("window_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"))); ADD_SIGNAL(MethodInfo("files_dropped", PropertyInfo(Variant::PACKED_STRING_ARRAY, "files"))); diff --git a/scene/main/window.h b/scene/main/window.h index 494c386606..4f31d9cd1f 100644 --- a/scene/main/window.h +++ b/scene/main/window.h @@ -32,10 +32,12 @@ #define WINDOW_H #include "scene/main/viewport.h" -#include "scene/resources/theme.h" -#include "servers/display_server.h" class Control; +class Font; +class StyleBox; +class Theme; + class Window : public Viewport { GDCLASS(Window, Viewport) public: @@ -103,6 +105,8 @@ private: LayoutDirection layout_dir = LAYOUT_DIRECTION_INHERITED; + bool auto_translate = true; + void _update_child_controls(); Size2i content_scale_size; @@ -130,7 +134,7 @@ private: Ref<Theme> theme; Control *theme_owner = nullptr; Window *theme_owner_window = nullptr; - StringName theme_custom_type; + StringName theme_type_variation; Viewport *embedder = nullptr; @@ -151,6 +155,7 @@ protected: virtual Size2 _get_contents_minimum_size() const; static void _bind_methods(); void _notification(int p_what); + virtual void _validate_property(PropertyInfo &property) const override; virtual void add_child_notify(Node *p_child) override; virtual void remove_child_notify(Node *p_child) override; @@ -242,8 +247,8 @@ public: void set_theme(const Ref<Theme> &p_theme); Ref<Theme> get_theme() const; - void set_theme_custom_type(const StringName &p_theme_type); - StringName get_theme_custom_type() const; + void set_theme_type_variation(const StringName &p_theme_type); + StringName get_theme_type_variation() const; _FORCE_INLINE_ void _get_theme_type_dependencies(const StringName &p_theme_type, List<StringName> *p_list) const; Size2 get_contents_minimum_size() const; @@ -255,6 +260,10 @@ public: LayoutDirection get_layout_direction() const; bool is_layout_rtl() const; + void set_auto_translate(bool p_enable); + bool is_auto_translating() const; + _FORCE_INLINE_ String atr(const String p_string) const { return is_auto_translating() ? tr(p_string) : p_string; }; + Rect2i get_usable_parent_rect() const; Ref<Texture2D> get_theme_icon(const StringName &p_name, const StringName &p_theme_type = StringName()) const; diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 24a9b12733..69f20ac990 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -49,6 +49,7 @@ #include "scene/2d/light_2d.h" #include "scene/2d/light_occluder_2d.h" #include "scene/2d/line_2d.h" +#include "scene/2d/listener_2d.h" #include "scene/2d/mesh_instance_2d.h" #include "scene/2d/multimesh_instance_2d.h" #include "scene/2d/navigation_agent_2d.h" @@ -148,7 +149,6 @@ #include "scene/resources/gradient.h" #include "scene/resources/height_map_shape_3d.h" #include "scene/resources/immediate_mesh.h" -#include "scene/resources/line_shape_2d.h" #include "scene/resources/material.h" #include "scene/resources/mesh.h" #include "scene/resources/mesh_data_tool.h" @@ -158,11 +158,11 @@ #include "scene/resources/physics_material.h" #include "scene/resources/polygon_path_finder.h" #include "scene/resources/primitive_meshes.h" -#include "scene/resources/ray_shape_2d.h" -#include "scene/resources/ray_shape_3d.h" #include "scene/resources/rectangle_shape_2d.h" #include "scene/resources/resource_format_text.h" #include "scene/resources/segment_shape_2d.h" +#include "scene/resources/separation_ray_shape_2d.h" +#include "scene/resources/separation_ray_shape_3d.h" #include "scene/resources/skeleton_modification_2d.h" #include "scene/resources/skeleton_modification_2d_ccdik.h" #include "scene/resources/skeleton_modification_2d_fabrik.h" @@ -171,7 +171,15 @@ #include "scene/resources/skeleton_modification_2d_physicalbones.h" #include "scene/resources/skeleton_modification_2d_stackholder.h" #include "scene/resources/skeleton_modification_2d_twoboneik.h" +#include "scene/resources/skeleton_modification_3d.h" +#include "scene/resources/skeleton_modification_3d_ccdik.h" +#include "scene/resources/skeleton_modification_3d_fabrik.h" +#include "scene/resources/skeleton_modification_3d_jiggle.h" +#include "scene/resources/skeleton_modification_3d_lookat.h" +#include "scene/resources/skeleton_modification_3d_stackholder.h" +#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/sky.h" #include "scene/resources/sky_material.h" #include "scene/resources/sphere_shape_3d.h" @@ -189,7 +197,8 @@ #include "scene/resources/visual_shader_sdf_nodes.h" #include "scene/resources/world_2d.h" #include "scene/resources/world_3d.h" -#include "scene/resources/world_margin_shape_3d.h" +#include "scene/resources/world_boundary_shape_2d.h" +#include "scene/resources/world_boundary_shape_3d.h" #include "scene/scene_string_names.h" #include "scene/main/shader_globals_override.h" @@ -241,12 +250,6 @@ static Ref<ResourceFormatSaverText> resource_saver_text; static Ref<ResourceFormatLoaderText> resource_loader_text; -static Ref<ResourceFormatLoaderFont> resource_loader_font; - -#ifndef DISABLE_DEPRECATED -static Ref<ResourceFormatLoaderCompatFont> resource_loader_compat_font; -#endif /* DISABLE_DEPRECATED */ - static Ref<ResourceFormatLoaderStreamTexture2D> resource_loader_stream_texture; static Ref<ResourceFormatLoaderStreamTextureLayered> resource_loader_texture_layered; static Ref<ResourceFormatLoaderStreamTexture3D> resource_loader_texture_3d; @@ -261,14 +264,6 @@ void register_scene_types() { Node::init_node_hrcr(); - resource_loader_font.instantiate(); - ResourceLoader::add_resource_format_loader(resource_loader_font); - -#ifndef DISABLE_DEPRECATED - resource_loader_compat_font.instantiate(); - ResourceLoader::add_resource_format_loader(resource_loader_compat_font); -#endif /* DISABLE_DEPRECATED */ - resource_loader_stream_texture.instantiate(); ResourceLoader::add_resource_format_loader(resource_loader_stream_texture); @@ -292,108 +287,107 @@ void register_scene_types() { OS::get_singleton()->yield(); //may take time to init - ClassDB::register_class<Object>(); + GDREGISTER_CLASS(Object); - ClassDB::register_class<Node>(); - ClassDB::register_virtual_class<InstancePlaceholder>(); + GDREGISTER_CLASS(Node); + GDREGISTER_VIRTUAL_CLASS(InstancePlaceholder); - ClassDB::register_virtual_class<Viewport>(); - ClassDB::register_class<SubViewport>(); - ClassDB::register_class<ViewportTexture>(); - ClassDB::register_class<HTTPRequest>(); - ClassDB::register_class<Timer>(); - ClassDB::register_class<CanvasLayer>(); - ClassDB::register_class<CanvasModulate>(); - ClassDB::register_class<ResourcePreloader>(); - ClassDB::register_class<Window>(); + GDREGISTER_VIRTUAL_CLASS(Viewport); + GDREGISTER_CLASS(SubViewport); + GDREGISTER_CLASS(ViewportTexture); + GDREGISTER_CLASS(HTTPRequest); + GDREGISTER_CLASS(Timer); + GDREGISTER_CLASS(CanvasLayer); + GDREGISTER_CLASS(CanvasModulate); + GDREGISTER_CLASS(ResourcePreloader); + GDREGISTER_CLASS(Window); /* REGISTER GUI */ - ClassDB::register_class<ButtonGroup>(); - ClassDB::register_virtual_class<BaseButton>(); + GDREGISTER_CLASS(ButtonGroup); + GDREGISTER_VIRTUAL_CLASS(BaseButton); OS::get_singleton()->yield(); //may take time to init - ClassDB::register_class<Shortcut>(); - ClassDB::register_class<Control>(); - ClassDB::register_class<Button>(); - ClassDB::register_class<Label>(); - ClassDB::register_virtual_class<ScrollBar>(); - ClassDB::register_class<HScrollBar>(); - ClassDB::register_class<VScrollBar>(); - ClassDB::register_class<ProgressBar>(); - ClassDB::register_virtual_class<Slider>(); - ClassDB::register_class<HSlider>(); - ClassDB::register_class<VSlider>(); - ClassDB::register_class<Popup>(); - ClassDB::register_class<PopupPanel>(); - ClassDB::register_class<MenuButton>(); - ClassDB::register_class<CheckBox>(); - ClassDB::register_class<CheckButton>(); - ClassDB::register_class<LinkButton>(); - ClassDB::register_class<Panel>(); - ClassDB::register_virtual_class<Range>(); + GDREGISTER_CLASS(Control); + GDREGISTER_CLASS(Button); + GDREGISTER_CLASS(Label); + GDREGISTER_VIRTUAL_CLASS(ScrollBar); + GDREGISTER_CLASS(HScrollBar); + GDREGISTER_CLASS(VScrollBar); + GDREGISTER_CLASS(ProgressBar); + GDREGISTER_VIRTUAL_CLASS(Slider); + GDREGISTER_CLASS(HSlider); + GDREGISTER_CLASS(VSlider); + GDREGISTER_CLASS(Popup); + GDREGISTER_CLASS(PopupPanel); + GDREGISTER_CLASS(MenuButton); + GDREGISTER_CLASS(CheckBox); + GDREGISTER_CLASS(CheckButton); + GDREGISTER_CLASS(LinkButton); + GDREGISTER_CLASS(Panel); + GDREGISTER_VIRTUAL_CLASS(Range); OS::get_singleton()->yield(); //may take time to init - ClassDB::register_class<TextureRect>(); - ClassDB::register_class<ColorRect>(); - ClassDB::register_class<NinePatchRect>(); - ClassDB::register_class<ReferenceRect>(); - ClassDB::register_class<AspectRatioContainer>(); - ClassDB::register_class<TabContainer>(); - ClassDB::register_class<Tabs>(); - ClassDB::register_virtual_class<Separator>(); - ClassDB::register_class<HSeparator>(); - ClassDB::register_class<VSeparator>(); - ClassDB::register_class<TextureButton>(); - ClassDB::register_class<Container>(); - ClassDB::register_virtual_class<BoxContainer>(); - ClassDB::register_class<HBoxContainer>(); - ClassDB::register_class<VBoxContainer>(); - ClassDB::register_class<GridContainer>(); - ClassDB::register_class<CenterContainer>(); - ClassDB::register_class<ScrollContainer>(); - ClassDB::register_class<PanelContainer>(); + GDREGISTER_CLASS(TextureRect); + GDREGISTER_CLASS(ColorRect); + GDREGISTER_CLASS(NinePatchRect); + GDREGISTER_CLASS(ReferenceRect); + GDREGISTER_CLASS(AspectRatioContainer); + GDREGISTER_CLASS(TabContainer); + GDREGISTER_CLASS(Tabs); + GDREGISTER_VIRTUAL_CLASS(Separator); + GDREGISTER_CLASS(HSeparator); + GDREGISTER_CLASS(VSeparator); + GDREGISTER_CLASS(TextureButton); + GDREGISTER_CLASS(Container); + GDREGISTER_VIRTUAL_CLASS(BoxContainer); + GDREGISTER_CLASS(HBoxContainer); + GDREGISTER_CLASS(VBoxContainer); + GDREGISTER_CLASS(GridContainer); + GDREGISTER_CLASS(CenterContainer); + GDREGISTER_CLASS(ScrollContainer); + GDREGISTER_CLASS(PanelContainer); OS::get_singleton()->yield(); //may take time to init - ClassDB::register_class<TextureProgressBar>(); - ClassDB::register_class<ItemList>(); + GDREGISTER_CLASS(TextureProgressBar); + GDREGISTER_CLASS(ItemList); - ClassDB::register_class<LineEdit>(); - ClassDB::register_class<VideoPlayer>(); + GDREGISTER_CLASS(LineEdit); + GDREGISTER_CLASS(VideoPlayer); #ifndef ADVANCED_GUI_DISABLED - ClassDB::register_class<FileDialog>(); - - ClassDB::register_class<PopupMenu>(); - ClassDB::register_class<Tree>(); - - ClassDB::register_class<TextEdit>(); - ClassDB::register_class<CodeEdit>(); - ClassDB::register_class<SyntaxHighlighter>(); - ClassDB::register_class<CodeHighlighter>(); - - ClassDB::register_virtual_class<TreeItem>(); - ClassDB::register_class<OptionButton>(); - ClassDB::register_class<SpinBox>(); - ClassDB::register_class<ColorPicker>(); - ClassDB::register_class<ColorPickerButton>(); - ClassDB::register_class<RichTextLabel>(); - ClassDB::register_class<RichTextEffect>(); - ClassDB::register_class<CharFXTransform>(); - - ClassDB::register_class<AcceptDialog>(); - ClassDB::register_class<ConfirmationDialog>(); - - ClassDB::register_class<MarginContainer>(); - ClassDB::register_class<SubViewportContainer>(); - ClassDB::register_virtual_class<SplitContainer>(); - ClassDB::register_class<HSplitContainer>(); - ClassDB::register_class<VSplitContainer>(); - ClassDB::register_class<GraphNode>(); - ClassDB::register_class<GraphEdit>(); + GDREGISTER_CLASS(FileDialog); + + GDREGISTER_CLASS(PopupMenu); + GDREGISTER_CLASS(Tree); + + GDREGISTER_CLASS(TextEdit); + GDREGISTER_CLASS(CodeEdit); + GDREGISTER_CLASS(SyntaxHighlighter); + GDREGISTER_CLASS(CodeHighlighter); + + GDREGISTER_VIRTUAL_CLASS(TreeItem); + GDREGISTER_CLASS(OptionButton); + GDREGISTER_CLASS(SpinBox); + GDREGISTER_CLASS(ColorPicker); + GDREGISTER_CLASS(ColorPickerButton); + GDREGISTER_CLASS(RichTextLabel); + GDREGISTER_CLASS(RichTextEffect); + GDREGISTER_CLASS(CharFXTransform); + + GDREGISTER_CLASS(AcceptDialog); + GDREGISTER_CLASS(ConfirmationDialog); + + GDREGISTER_CLASS(MarginContainer); + GDREGISTER_CLASS(SubViewportContainer); + GDREGISTER_VIRTUAL_CLASS(SplitContainer); + GDREGISTER_CLASS(HSplitContainer); + GDREGISTER_CLASS(VSplitContainer); + GDREGISTER_CLASS(GraphNode); + GDREGISTER_CLASS(GraphEdit); OS::get_singleton()->yield(); //may take time to init @@ -406,450 +400,462 @@ void register_scene_types() { /* REGISTER ANIMATION */ - ClassDB::register_class<AnimationPlayer>(); - ClassDB::register_class<Tween>(); - ClassDB::register_virtual_class<Tweener>(); - ClassDB::register_class<PropertyTweener>(); - ClassDB::register_class<IntervalTweener>(); - ClassDB::register_class<CallbackTweener>(); - ClassDB::register_class<MethodTweener>(); - - ClassDB::register_class<AnimationTree>(); - ClassDB::register_class<AnimationNode>(); - ClassDB::register_class<AnimationRootNode>(); - ClassDB::register_class<AnimationNodeBlendTree>(); - ClassDB::register_class<AnimationNodeBlendSpace1D>(); - ClassDB::register_class<AnimationNodeBlendSpace2D>(); - ClassDB::register_class<AnimationNodeStateMachine>(); - ClassDB::register_class<AnimationNodeStateMachinePlayback>(); - - ClassDB::register_class<AnimationNodeStateMachineTransition>(); - ClassDB::register_class<AnimationNodeOutput>(); - ClassDB::register_class<AnimationNodeOneShot>(); - ClassDB::register_class<AnimationNodeAnimation>(); - ClassDB::register_class<AnimationNodeAdd2>(); - ClassDB::register_class<AnimationNodeAdd3>(); - ClassDB::register_class<AnimationNodeBlend2>(); - ClassDB::register_class<AnimationNodeBlend3>(); - ClassDB::register_class<AnimationNodeTimeScale>(); - ClassDB::register_class<AnimationNodeTimeSeek>(); - ClassDB::register_class<AnimationNodeTransition>(); - - ClassDB::register_class<ShaderGlobalsOverride>(); //can be used in any shader + GDREGISTER_CLASS(AnimationPlayer); + GDREGISTER_CLASS(Tween); + GDREGISTER_VIRTUAL_CLASS(Tweener); + GDREGISTER_CLASS(PropertyTweener); + GDREGISTER_CLASS(IntervalTweener); + GDREGISTER_CLASS(CallbackTweener); + GDREGISTER_CLASS(MethodTweener); + + GDREGISTER_CLASS(AnimationTree); + GDREGISTER_CLASS(AnimationNode); + GDREGISTER_CLASS(AnimationRootNode); + GDREGISTER_CLASS(AnimationNodeBlendTree); + GDREGISTER_CLASS(AnimationNodeBlendSpace1D); + GDREGISTER_CLASS(AnimationNodeBlendSpace2D); + GDREGISTER_CLASS(AnimationNodeStateMachine); + GDREGISTER_CLASS(AnimationNodeStateMachinePlayback); + + GDREGISTER_CLASS(AnimationNodeStateMachineTransition); + GDREGISTER_CLASS(AnimationNodeOutput); + GDREGISTER_CLASS(AnimationNodeOneShot); + GDREGISTER_CLASS(AnimationNodeAnimation); + GDREGISTER_CLASS(AnimationNodeAdd2); + GDREGISTER_CLASS(AnimationNodeAdd3); + GDREGISTER_CLASS(AnimationNodeBlend2); + GDREGISTER_CLASS(AnimationNodeBlend3); + GDREGISTER_CLASS(AnimationNodeTimeScale); + GDREGISTER_CLASS(AnimationNodeTimeSeek); + GDREGISTER_CLASS(AnimationNodeTransition); + + GDREGISTER_CLASS(ShaderGlobalsOverride); //can be used in any shader OS::get_singleton()->yield(); //may take time to init /* REGISTER 3D */ #ifndef _3D_DISABLED - ClassDB::register_class<Node3D>(); - ClassDB::register_virtual_class<Node3DGizmo>(); - ClassDB::register_class<Skin>(); - ClassDB::register_virtual_class<SkinReference>(); - ClassDB::register_class<Skeleton3D>(); - ClassDB::register_virtual_class<VisualInstance3D>(); - ClassDB::register_virtual_class<GeometryInstance3D>(); - ClassDB::register_class<Camera3D>(); - ClassDB::register_class<ClippedCamera3D>(); - ClassDB::register_class<Listener3D>(); - ClassDB::register_class<XRCamera3D>(); - ClassDB::register_class<XRController3D>(); - ClassDB::register_class<XRAnchor3D>(); - ClassDB::register_class<XROrigin3D>(); - ClassDB::register_class<MeshInstance3D>(); - ClassDB::register_class<OccluderInstance3D>(); - ClassDB::register_class<Occluder3D>(); - ClassDB::register_virtual_class<SpriteBase3D>(); - ClassDB::register_class<Sprite3D>(); - ClassDB::register_class<AnimatedSprite3D>(); - ClassDB::register_virtual_class<Light3D>(); - ClassDB::register_class<DirectionalLight3D>(); - ClassDB::register_class<OmniLight3D>(); - ClassDB::register_class<SpotLight3D>(); - ClassDB::register_class<ReflectionProbe>(); - ClassDB::register_class<Decal>(); - ClassDB::register_class<VoxelGI>(); - ClassDB::register_class<VoxelGIData>(); - ClassDB::register_class<LightmapGI>(); - ClassDB::register_class<LightmapGIData>(); - ClassDB::register_class<LightmapProbe>(); - ClassDB::register_virtual_class<Lightmapper>(); - ClassDB::register_class<GPUParticles3D>(); - ClassDB::register_virtual_class<GPUParticlesCollision3D>(); - ClassDB::register_class<GPUParticlesCollisionBox>(); - ClassDB::register_class<GPUParticlesCollisionSphere>(); - ClassDB::register_class<GPUParticlesCollisionSDF>(); - ClassDB::register_class<GPUParticlesCollisionHeightField>(); - ClassDB::register_virtual_class<GPUParticlesAttractor3D>(); - ClassDB::register_class<GPUParticlesAttractorBox>(); - ClassDB::register_class<GPUParticlesAttractorSphere>(); - ClassDB::register_class<GPUParticlesAttractorVectorField>(); - ClassDB::register_class<CPUParticles3D>(); - ClassDB::register_class<Position3D>(); - - ClassDB::register_class<RootMotionView>(); + GDREGISTER_CLASS(Node3D); + GDREGISTER_VIRTUAL_CLASS(Node3DGizmo); + GDREGISTER_CLASS(Skin); + GDREGISTER_VIRTUAL_CLASS(SkinReference); + GDREGISTER_CLASS(Skeleton3D); + GDREGISTER_VIRTUAL_CLASS(VisualInstance3D); + GDREGISTER_VIRTUAL_CLASS(GeometryInstance3D); + GDREGISTER_CLASS(Camera3D); + GDREGISTER_CLASS(ClippedCamera3D); + GDREGISTER_CLASS(Listener3D); + GDREGISTER_CLASS(XRCamera3D); + GDREGISTER_CLASS(XRController3D); + GDREGISTER_CLASS(XRAnchor3D); + GDREGISTER_CLASS(XROrigin3D); + GDREGISTER_CLASS(MeshInstance3D); + GDREGISTER_CLASS(OccluderInstance3D); + GDREGISTER_CLASS(Occluder3D); + GDREGISTER_VIRTUAL_CLASS(SpriteBase3D); + GDREGISTER_CLASS(Sprite3D); + GDREGISTER_CLASS(AnimatedSprite3D); + GDREGISTER_VIRTUAL_CLASS(Light3D); + GDREGISTER_CLASS(DirectionalLight3D); + GDREGISTER_CLASS(OmniLight3D); + GDREGISTER_CLASS(SpotLight3D); + GDREGISTER_CLASS(ReflectionProbe); + GDREGISTER_CLASS(Decal); + GDREGISTER_CLASS(VoxelGI); + GDREGISTER_CLASS(VoxelGIData); + GDREGISTER_CLASS(LightmapGI); + GDREGISTER_CLASS(LightmapGIData); + GDREGISTER_CLASS(LightmapProbe); + GDREGISTER_VIRTUAL_CLASS(Lightmapper); + GDREGISTER_CLASS(GPUParticles3D); + GDREGISTER_VIRTUAL_CLASS(GPUParticlesCollision3D); + GDREGISTER_CLASS(GPUParticlesCollisionBox); + GDREGISTER_CLASS(GPUParticlesCollisionSphere); + GDREGISTER_CLASS(GPUParticlesCollisionSDF); + GDREGISTER_CLASS(GPUParticlesCollisionHeightField); + GDREGISTER_VIRTUAL_CLASS(GPUParticlesAttractor3D); + GDREGISTER_CLASS(GPUParticlesAttractorBox); + GDREGISTER_CLASS(GPUParticlesAttractorSphere); + GDREGISTER_CLASS(GPUParticlesAttractorVectorField); + GDREGISTER_CLASS(CPUParticles3D); + GDREGISTER_CLASS(Position3D); + + GDREGISTER_CLASS(RootMotionView); ClassDB::set_class_enabled("RootMotionView", false); //disabled by default, enabled by editor OS::get_singleton()->yield(); //may take time to init - ClassDB::register_virtual_class<CollisionObject3D>(); - ClassDB::register_virtual_class<PhysicsBody3D>(); - ClassDB::register_class<StaticBody3D>(); - ClassDB::register_class<RigidBody3D>(); - ClassDB::register_class<KinematicCollision3D>(); - ClassDB::register_class<CharacterBody3D>(); - ClassDB::register_class<SpringArm3D>(); - - ClassDB::register_class<PhysicalBone3D>(); - ClassDB::register_class<SoftBody3D>(); - - ClassDB::register_class<SkeletonIK3D>(); - ClassDB::register_class<BoneAttachment3D>(); - - ClassDB::register_class<VehicleBody3D>(); - ClassDB::register_class<VehicleWheel3D>(); - ClassDB::register_class<Area3D>(); - ClassDB::register_class<ProximityGroup3D>(); - ClassDB::register_class<CollisionShape3D>(); - ClassDB::register_class<CollisionPolygon3D>(); - ClassDB::register_class<RayCast3D>(); - ClassDB::register_class<MultiMeshInstance3D>(); - - ClassDB::register_class<Curve3D>(); - ClassDB::register_class<Path3D>(); - ClassDB::register_class<PathFollow3D>(); - ClassDB::register_class<VisibleOnScreenNotifier3D>(); - ClassDB::register_class<VisibleOnScreenEnabler3D>(); - ClassDB::register_class<WorldEnvironment>(); - ClassDB::register_class<RemoteTransform3D>(); - - ClassDB::register_virtual_class<Joint3D>(); - ClassDB::register_class<PinJoint3D>(); - ClassDB::register_class<HingeJoint3D>(); - ClassDB::register_class<SliderJoint3D>(); - ClassDB::register_class<ConeTwistJoint3D>(); - ClassDB::register_class<Generic6DOFJoint3D>(); - - ClassDB::register_class<NavigationRegion3D>(); - ClassDB::register_class<NavigationAgent3D>(); - ClassDB::register_class<NavigationObstacle3D>(); + GDREGISTER_VIRTUAL_CLASS(CollisionObject3D); + GDREGISTER_VIRTUAL_CLASS(PhysicsBody3D); + GDREGISTER_CLASS(StaticBody3D); + GDREGISTER_CLASS(AnimatableBody3D); + GDREGISTER_CLASS(RigidBody3D); + GDREGISTER_CLASS(KinematicCollision3D); + GDREGISTER_CLASS(CharacterBody3D); + GDREGISTER_CLASS(SpringArm3D); + + GDREGISTER_CLASS(PhysicalBone3D); + GDREGISTER_CLASS(SoftBody3D); + + GDREGISTER_CLASS(SkeletonIK3D); + GDREGISTER_CLASS(BoneAttachment3D); + + GDREGISTER_CLASS(VehicleBody3D); + GDREGISTER_CLASS(VehicleWheel3D); + GDREGISTER_CLASS(Area3D); + GDREGISTER_CLASS(ProximityGroup3D); + GDREGISTER_CLASS(CollisionShape3D); + GDREGISTER_CLASS(CollisionPolygon3D); + GDREGISTER_CLASS(RayCast3D); + GDREGISTER_CLASS(MultiMeshInstance3D); + + GDREGISTER_CLASS(Curve3D); + GDREGISTER_CLASS(Path3D); + GDREGISTER_CLASS(PathFollow3D); + GDREGISTER_CLASS(VisibleOnScreenNotifier3D); + GDREGISTER_CLASS(VisibleOnScreenEnabler3D); + GDREGISTER_CLASS(WorldEnvironment); + GDREGISTER_CLASS(RemoteTransform3D); + + GDREGISTER_VIRTUAL_CLASS(Joint3D); + GDREGISTER_CLASS(PinJoint3D); + GDREGISTER_CLASS(HingeJoint3D); + GDREGISTER_CLASS(SliderJoint3D); + GDREGISTER_CLASS(ConeTwistJoint3D); + GDREGISTER_CLASS(Generic6DOFJoint3D); + + GDREGISTER_CLASS(NavigationRegion3D); + GDREGISTER_CLASS(NavigationAgent3D); + GDREGISTER_CLASS(NavigationObstacle3D); OS::get_singleton()->yield(); //may take time to init #endif /* REGISTER SHADER */ - ClassDB::register_class<Shader>(); - ClassDB::register_class<VisualShader>(); - ClassDB::register_virtual_class<VisualShaderNode>(); - ClassDB::register_class<VisualShaderNodeCustom>(); - ClassDB::register_class<VisualShaderNodeInput>(); - ClassDB::register_virtual_class<VisualShaderNodeOutput>(); - ClassDB::register_virtual_class<VisualShaderNodeResizableBase>(); - ClassDB::register_virtual_class<VisualShaderNodeGroupBase>(); - ClassDB::register_virtual_class<VisualShaderNodeConstant>(); - ClassDB::register_class<VisualShaderNodeComment>(); - ClassDB::register_class<VisualShaderNodeFloatConstant>(); - ClassDB::register_class<VisualShaderNodeIntConstant>(); - ClassDB::register_class<VisualShaderNodeBooleanConstant>(); - ClassDB::register_class<VisualShaderNodeColorConstant>(); - ClassDB::register_class<VisualShaderNodeVec3Constant>(); - ClassDB::register_class<VisualShaderNodeTransformConstant>(); - ClassDB::register_class<VisualShaderNodeFloatOp>(); - ClassDB::register_class<VisualShaderNodeIntOp>(); - ClassDB::register_class<VisualShaderNodeVectorOp>(); - ClassDB::register_class<VisualShaderNodeColorOp>(); - ClassDB::register_class<VisualShaderNodeTransformMult>(); - ClassDB::register_class<VisualShaderNodeTransformVecMult>(); - ClassDB::register_class<VisualShaderNodeFloatFunc>(); - ClassDB::register_class<VisualShaderNodeIntFunc>(); - ClassDB::register_class<VisualShaderNodeVectorFunc>(); - ClassDB::register_class<VisualShaderNodeColorFunc>(); - ClassDB::register_class<VisualShaderNodeTransformFunc>(); - ClassDB::register_class<VisualShaderNodeUVFunc>(); - ClassDB::register_class<VisualShaderNodeDotProduct>(); - ClassDB::register_class<VisualShaderNodeVectorLen>(); - ClassDB::register_class<VisualShaderNodeDeterminant>(); - ClassDB::register_class<VisualShaderNodeScalarDerivativeFunc>(); - ClassDB::register_class<VisualShaderNodeVectorDerivativeFunc>(); - ClassDB::register_class<VisualShaderNodeClamp>(); - ClassDB::register_class<VisualShaderNodeFaceForward>(); - ClassDB::register_class<VisualShaderNodeOuterProduct>(); - ClassDB::register_class<VisualShaderNodeSmoothStep>(); - ClassDB::register_class<VisualShaderNodeStep>(); - ClassDB::register_class<VisualShaderNodeVectorDistance>(); - ClassDB::register_class<VisualShaderNodeVectorRefract>(); - ClassDB::register_class<VisualShaderNodeMix>(); - ClassDB::register_class<VisualShaderNodeVectorCompose>(); - ClassDB::register_class<VisualShaderNodeTransformCompose>(); - ClassDB::register_class<VisualShaderNodeVectorDecompose>(); - ClassDB::register_class<VisualShaderNodeTransformDecompose>(); - ClassDB::register_class<VisualShaderNodeTexture>(); - ClassDB::register_class<VisualShaderNodeCurveTexture>(); - ClassDB::register_virtual_class<VisualShaderNodeSample3D>(); - ClassDB::register_class<VisualShaderNodeTexture2DArray>(); - ClassDB::register_class<VisualShaderNodeTexture3D>(); - ClassDB::register_class<VisualShaderNodeCubemap>(); - ClassDB::register_virtual_class<VisualShaderNodeUniform>(); - ClassDB::register_class<VisualShaderNodeUniformRef>(); - ClassDB::register_class<VisualShaderNodeFloatUniform>(); - ClassDB::register_class<VisualShaderNodeIntUniform>(); - ClassDB::register_class<VisualShaderNodeBooleanUniform>(); - ClassDB::register_class<VisualShaderNodeColorUniform>(); - ClassDB::register_class<VisualShaderNodeVec3Uniform>(); - ClassDB::register_class<VisualShaderNodeTransformUniform>(); - ClassDB::register_class<VisualShaderNodeTextureUniform>(); - ClassDB::register_class<VisualShaderNodeTextureUniformTriplanar>(); - ClassDB::register_class<VisualShaderNodeTexture2DArrayUniform>(); - ClassDB::register_class<VisualShaderNodeTexture3DUniform>(); - ClassDB::register_class<VisualShaderNodeCubemapUniform>(); - ClassDB::register_class<VisualShaderNodeIf>(); - ClassDB::register_class<VisualShaderNodeSwitch>(); - ClassDB::register_class<VisualShaderNodeFresnel>(); - ClassDB::register_class<VisualShaderNodeExpression>(); - ClassDB::register_class<VisualShaderNodeGlobalExpression>(); - ClassDB::register_class<VisualShaderNodeIs>(); - ClassDB::register_class<VisualShaderNodeCompare>(); - ClassDB::register_class<VisualShaderNodeMultiplyAdd>(); - ClassDB::register_class<VisualShaderNodeBillboard>(); - - ClassDB::register_class<VisualShaderNodeSDFToScreenUV>(); - ClassDB::register_class<VisualShaderNodeScreenUVToSDF>(); - ClassDB::register_class<VisualShaderNodeTextureSDF>(); - ClassDB::register_class<VisualShaderNodeTextureSDFNormal>(); - ClassDB::register_class<VisualShaderNodeSDFRaymarch>(); - - ClassDB::register_class<VisualShaderNodeParticleOutput>(); - ClassDB::register_virtual_class<VisualShaderNodeParticleEmitter>(); - ClassDB::register_class<VisualShaderNodeParticleSphereEmitter>(); - ClassDB::register_class<VisualShaderNodeParticleBoxEmitter>(); - ClassDB::register_class<VisualShaderNodeParticleRingEmitter>(); - ClassDB::register_class<VisualShaderNodeParticleMultiplyByAxisAngle>(); - ClassDB::register_class<VisualShaderNodeParticleConeVelocity>(); - ClassDB::register_class<VisualShaderNodeParticleRandomness>(); - ClassDB::register_class<VisualShaderNodeParticleAccelerator>(); - ClassDB::register_class<VisualShaderNodeParticleEmit>(); - - ClassDB::register_class<ShaderMaterial>(); - ClassDB::register_virtual_class<CanvasItem>(); - ClassDB::register_class<CanvasTexture>(); - ClassDB::register_class<CanvasItemMaterial>(); + GDREGISTER_CLASS(Shader); + GDREGISTER_CLASS(VisualShader); + GDREGISTER_VIRTUAL_CLASS(VisualShaderNode); + GDREGISTER_CLASS(VisualShaderNodeCustom); + GDREGISTER_CLASS(VisualShaderNodeInput); + GDREGISTER_VIRTUAL_CLASS(VisualShaderNodeOutput); + GDREGISTER_VIRTUAL_CLASS(VisualShaderNodeResizableBase); + GDREGISTER_VIRTUAL_CLASS(VisualShaderNodeGroupBase); + GDREGISTER_VIRTUAL_CLASS(VisualShaderNodeConstant); + GDREGISTER_CLASS(VisualShaderNodeComment); + GDREGISTER_CLASS(VisualShaderNodeFloatConstant); + GDREGISTER_CLASS(VisualShaderNodeIntConstant); + GDREGISTER_CLASS(VisualShaderNodeBooleanConstant); + GDREGISTER_CLASS(VisualShaderNodeColorConstant); + GDREGISTER_CLASS(VisualShaderNodeVec3Constant); + GDREGISTER_CLASS(VisualShaderNodeTransformConstant); + GDREGISTER_CLASS(VisualShaderNodeFloatOp); + GDREGISTER_CLASS(VisualShaderNodeIntOp); + GDREGISTER_CLASS(VisualShaderNodeVectorOp); + GDREGISTER_CLASS(VisualShaderNodeColorOp); + GDREGISTER_CLASS(VisualShaderNodeTransformOp); + GDREGISTER_CLASS(VisualShaderNodeTransformVecMult); + GDREGISTER_CLASS(VisualShaderNodeFloatFunc); + GDREGISTER_CLASS(VisualShaderNodeIntFunc); + GDREGISTER_CLASS(VisualShaderNodeVectorFunc); + GDREGISTER_CLASS(VisualShaderNodeColorFunc); + GDREGISTER_CLASS(VisualShaderNodeTransformFunc); + GDREGISTER_CLASS(VisualShaderNodeUVFunc); + GDREGISTER_CLASS(VisualShaderNodeDotProduct); + GDREGISTER_CLASS(VisualShaderNodeVectorLen); + GDREGISTER_CLASS(VisualShaderNodeDeterminant); + GDREGISTER_CLASS(VisualShaderNodeScalarDerivativeFunc); + GDREGISTER_CLASS(VisualShaderNodeVectorDerivativeFunc); + GDREGISTER_CLASS(VisualShaderNodeClamp); + GDREGISTER_CLASS(VisualShaderNodeFaceForward); + GDREGISTER_CLASS(VisualShaderNodeOuterProduct); + GDREGISTER_CLASS(VisualShaderNodeSmoothStep); + GDREGISTER_CLASS(VisualShaderNodeStep); + GDREGISTER_CLASS(VisualShaderNodeVectorDistance); + GDREGISTER_CLASS(VisualShaderNodeVectorRefract); + GDREGISTER_CLASS(VisualShaderNodeMix); + GDREGISTER_CLASS(VisualShaderNodeVectorCompose); + GDREGISTER_CLASS(VisualShaderNodeTransformCompose); + GDREGISTER_CLASS(VisualShaderNodeVectorDecompose); + GDREGISTER_CLASS(VisualShaderNodeTransformDecompose); + GDREGISTER_CLASS(VisualShaderNodeTexture); + GDREGISTER_CLASS(VisualShaderNodeCurveTexture); + GDREGISTER_CLASS(VisualShaderNodeCurveXYZTexture); + GDREGISTER_VIRTUAL_CLASS(VisualShaderNodeSample3D); + GDREGISTER_CLASS(VisualShaderNodeTexture2DArray); + GDREGISTER_CLASS(VisualShaderNodeTexture3D); + GDREGISTER_CLASS(VisualShaderNodeCubemap); + GDREGISTER_VIRTUAL_CLASS(VisualShaderNodeUniform); + GDREGISTER_CLASS(VisualShaderNodeUniformRef); + GDREGISTER_CLASS(VisualShaderNodeFloatUniform); + GDREGISTER_CLASS(VisualShaderNodeIntUniform); + GDREGISTER_CLASS(VisualShaderNodeBooleanUniform); + GDREGISTER_CLASS(VisualShaderNodeColorUniform); + GDREGISTER_CLASS(VisualShaderNodeVec3Uniform); + GDREGISTER_CLASS(VisualShaderNodeTransformUniform); + GDREGISTER_CLASS(VisualShaderNodeTextureUniform); + GDREGISTER_CLASS(VisualShaderNodeTextureUniformTriplanar); + GDREGISTER_CLASS(VisualShaderNodeTexture2DArrayUniform); + GDREGISTER_CLASS(VisualShaderNodeTexture3DUniform); + GDREGISTER_CLASS(VisualShaderNodeCubemapUniform); + GDREGISTER_CLASS(VisualShaderNodeIf); + GDREGISTER_CLASS(VisualShaderNodeSwitch); + GDREGISTER_CLASS(VisualShaderNodeFresnel); + GDREGISTER_CLASS(VisualShaderNodeExpression); + GDREGISTER_CLASS(VisualShaderNodeGlobalExpression); + GDREGISTER_CLASS(VisualShaderNodeIs); + GDREGISTER_CLASS(VisualShaderNodeCompare); + GDREGISTER_CLASS(VisualShaderNodeMultiplyAdd); + GDREGISTER_CLASS(VisualShaderNodeBillboard); + + GDREGISTER_CLASS(VisualShaderNodeSDFToScreenUV); + GDREGISTER_CLASS(VisualShaderNodeScreenUVToSDF); + GDREGISTER_CLASS(VisualShaderNodeTextureSDF); + GDREGISTER_CLASS(VisualShaderNodeTextureSDFNormal); + GDREGISTER_CLASS(VisualShaderNodeSDFRaymarch); + + GDREGISTER_CLASS(VisualShaderNodeParticleOutput); + GDREGISTER_VIRTUAL_CLASS(VisualShaderNodeParticleEmitter); + GDREGISTER_CLASS(VisualShaderNodeParticleSphereEmitter); + GDREGISTER_CLASS(VisualShaderNodeParticleBoxEmitter); + GDREGISTER_CLASS(VisualShaderNodeParticleRingEmitter); + GDREGISTER_CLASS(VisualShaderNodeParticleMultiplyByAxisAngle); + GDREGISTER_CLASS(VisualShaderNodeParticleConeVelocity); + GDREGISTER_CLASS(VisualShaderNodeParticleRandomness); + GDREGISTER_CLASS(VisualShaderNodeParticleAccelerator); + GDREGISTER_CLASS(VisualShaderNodeParticleEmit); + + GDREGISTER_CLASS(ShaderMaterial); + GDREGISTER_VIRTUAL_CLASS(CanvasItem); + GDREGISTER_CLASS(CanvasTexture); + GDREGISTER_CLASS(CanvasItemMaterial); SceneTree::add_idle_callback(CanvasItemMaterial::flush_changes); CanvasItemMaterial::init_shaders(); /* REGISTER 2D */ - ClassDB::register_class<Node2D>(); - ClassDB::register_class<CanvasGroup>(); - ClassDB::register_class<CPUParticles2D>(); - ClassDB::register_class<GPUParticles2D>(); - ClassDB::register_class<Sprite2D>(); - ClassDB::register_class<SpriteFrames>(); - ClassDB::register_class<AnimatedSprite2D>(); - ClassDB::register_class<Position2D>(); - ClassDB::register_class<Line2D>(); - ClassDB::register_class<MeshInstance2D>(); - ClassDB::register_class<MultiMeshInstance2D>(); - ClassDB::register_virtual_class<CollisionObject2D>(); - ClassDB::register_virtual_class<PhysicsBody2D>(); - ClassDB::register_class<StaticBody2D>(); - ClassDB::register_class<RigidBody2D>(); - ClassDB::register_class<CharacterBody2D>(); - ClassDB::register_class<KinematicCollision2D>(); - ClassDB::register_class<Area2D>(); - ClassDB::register_class<CollisionShape2D>(); - ClassDB::register_class<CollisionPolygon2D>(); - ClassDB::register_class<RayCast2D>(); - ClassDB::register_class<VisibleOnScreenNotifier2D>(); - ClassDB::register_class<VisibleOnScreenEnabler2D>(); - ClassDB::register_class<Polygon2D>(); - ClassDB::register_class<Skeleton2D>(); - ClassDB::register_class<Bone2D>(); - ClassDB::register_virtual_class<Light2D>(); - ClassDB::register_class<PointLight2D>(); - ClassDB::register_class<DirectionalLight2D>(); - ClassDB::register_class<LightOccluder2D>(); - ClassDB::register_class<OccluderPolygon2D>(); - ClassDB::register_class<BackBufferCopy>(); + GDREGISTER_CLASS(Node2D); + GDREGISTER_CLASS(CanvasGroup); + GDREGISTER_CLASS(CPUParticles2D); + GDREGISTER_CLASS(GPUParticles2D); + GDREGISTER_CLASS(Sprite2D); + GDREGISTER_CLASS(SpriteFrames); + GDREGISTER_CLASS(AnimatedSprite2D); + GDREGISTER_CLASS(Position2D); + GDREGISTER_CLASS(Line2D); + GDREGISTER_CLASS(MeshInstance2D); + GDREGISTER_CLASS(MultiMeshInstance2D); + GDREGISTER_VIRTUAL_CLASS(CollisionObject2D); + GDREGISTER_VIRTUAL_CLASS(PhysicsBody2D); + GDREGISTER_CLASS(StaticBody2D); + GDREGISTER_CLASS(AnimatableBody2D); + GDREGISTER_CLASS(RigidBody2D); + GDREGISTER_CLASS(CharacterBody2D); + GDREGISTER_CLASS(KinematicCollision2D); + GDREGISTER_CLASS(Area2D); + GDREGISTER_CLASS(CollisionShape2D); + GDREGISTER_CLASS(CollisionPolygon2D); + GDREGISTER_CLASS(RayCast2D); + GDREGISTER_CLASS(VisibleOnScreenNotifier2D); + GDREGISTER_CLASS(VisibleOnScreenEnabler2D); + GDREGISTER_CLASS(Polygon2D); + GDREGISTER_CLASS(Skeleton2D); + GDREGISTER_CLASS(Bone2D); + GDREGISTER_VIRTUAL_CLASS(Light2D); + GDREGISTER_CLASS(PointLight2D); + GDREGISTER_CLASS(DirectionalLight2D); + GDREGISTER_CLASS(LightOccluder2D); + GDREGISTER_CLASS(OccluderPolygon2D); + GDREGISTER_CLASS(BackBufferCopy); OS::get_singleton()->yield(); //may take time to init - ClassDB::register_class<Camera2D>(); - ClassDB::register_virtual_class<Joint2D>(); - ClassDB::register_class<PinJoint2D>(); - ClassDB::register_class<GrooveJoint2D>(); - ClassDB::register_class<DampedSpringJoint2D>(); - ClassDB::register_class<TileSet>(); - ClassDB::register_virtual_class<TileSetSource>(); - ClassDB::register_class<TileSetAtlasSource>(); - ClassDB::register_class<TileSetScenesCollectionSource>(); - ClassDB::register_class<TileData>(); - ClassDB::register_class<TileMap>(); - ClassDB::register_class<ParallaxBackground>(); - ClassDB::register_class<ParallaxLayer>(); - ClassDB::register_class<TouchScreenButton>(); - ClassDB::register_class<RemoteTransform2D>(); - - ClassDB::register_class<SkeletonModificationStack2D>(); - ClassDB::register_class<SkeletonModification2D>(); - ClassDB::register_class<SkeletonModification2DLookAt>(); - ClassDB::register_class<SkeletonModification2DCCDIK>(); - ClassDB::register_class<SkeletonModification2DFABRIK>(); - ClassDB::register_class<SkeletonModification2DJiggle>(); - ClassDB::register_class<SkeletonModification2DTwoBoneIK>(); - ClassDB::register_class<SkeletonModification2DStackHolder>(); - - ClassDB::register_class<PhysicalBone2D>(); - ClassDB::register_class<SkeletonModification2DPhysicalBones>(); + GDREGISTER_CLASS(Camera2D); + GDREGISTER_CLASS(Listener2D); + GDREGISTER_VIRTUAL_CLASS(Joint2D); + GDREGISTER_CLASS(PinJoint2D); + GDREGISTER_CLASS(GrooveJoint2D); + GDREGISTER_CLASS(DampedSpringJoint2D); + GDREGISTER_CLASS(TileSet); + GDREGISTER_VIRTUAL_CLASS(TileSetSource); + GDREGISTER_CLASS(TileSetAtlasSource); + GDREGISTER_CLASS(TileSetScenesCollectionSource); + GDREGISTER_CLASS(TileData); + GDREGISTER_CLASS(TileMap); + GDREGISTER_CLASS(ParallaxBackground); + GDREGISTER_CLASS(ParallaxLayer); + GDREGISTER_CLASS(TouchScreenButton); + GDREGISTER_CLASS(RemoteTransform2D); + + GDREGISTER_CLASS(SkeletonModificationStack2D); + GDREGISTER_CLASS(SkeletonModification2D); + GDREGISTER_CLASS(SkeletonModification2DLookAt); + GDREGISTER_CLASS(SkeletonModification2DCCDIK); + GDREGISTER_CLASS(SkeletonModification2DFABRIK); + GDREGISTER_CLASS(SkeletonModification2DJiggle); + GDREGISTER_CLASS(SkeletonModification2DTwoBoneIK); + GDREGISTER_CLASS(SkeletonModification2DStackHolder); + + GDREGISTER_CLASS(PhysicalBone2D); + GDREGISTER_CLASS(SkeletonModification2DPhysicalBones); OS::get_singleton()->yield(); //may take time to init /* REGISTER RESOURCES */ - ClassDB::register_virtual_class<Shader>(); - ClassDB::register_class<ParticlesMaterial>(); + GDREGISTER_VIRTUAL_CLASS(Shader); + GDREGISTER_CLASS(ParticlesMaterial); SceneTree::add_idle_callback(ParticlesMaterial::flush_changes); ParticlesMaterial::init_shaders(); - ClassDB::register_class<ProceduralSkyMaterial>(); - ClassDB::register_class<PanoramaSkyMaterial>(); - ClassDB::register_class<PhysicalSkyMaterial>(); + GDREGISTER_CLASS(ProceduralSkyMaterial); + GDREGISTER_CLASS(PanoramaSkyMaterial); + GDREGISTER_CLASS(PhysicalSkyMaterial); - ClassDB::register_virtual_class<Mesh>(); - ClassDB::register_class<ArrayMesh>(); - ClassDB::register_class<ImmediateMesh>(); - ClassDB::register_class<MultiMesh>(); - ClassDB::register_class<SurfaceTool>(); - ClassDB::register_class<MeshDataTool>(); + GDREGISTER_VIRTUAL_CLASS(Mesh); + GDREGISTER_CLASS(ArrayMesh); + GDREGISTER_CLASS(ImmediateMesh); + GDREGISTER_CLASS(MultiMesh); + GDREGISTER_CLASS(SurfaceTool); + GDREGISTER_CLASS(MeshDataTool); #ifndef _3D_DISABLED - ClassDB::register_virtual_class<PrimitiveMesh>(); - ClassDB::register_class<BoxMesh>(); - ClassDB::register_class<CapsuleMesh>(); - ClassDB::register_class<CylinderMesh>(); - ClassDB::register_class<PlaneMesh>(); - ClassDB::register_class<PrismMesh>(); - ClassDB::register_class<QuadMesh>(); - ClassDB::register_class<SphereMesh>(); - ClassDB::register_class<TubeTrailMesh>(); - ClassDB::register_class<RibbonTrailMesh>(); - ClassDB::register_class<PointMesh>(); - ClassDB::register_virtual_class<Material>(); - ClassDB::register_virtual_class<BaseMaterial3D>(); - ClassDB::register_class<StandardMaterial3D>(); - ClassDB::register_class<ORMMaterial3D>(); + GDREGISTER_VIRTUAL_CLASS(PrimitiveMesh); + GDREGISTER_CLASS(BoxMesh); + GDREGISTER_CLASS(CapsuleMesh); + GDREGISTER_CLASS(CylinderMesh); + GDREGISTER_CLASS(PlaneMesh); + GDREGISTER_CLASS(PrismMesh); + GDREGISTER_CLASS(QuadMesh); + GDREGISTER_CLASS(SphereMesh); + GDREGISTER_CLASS(TubeTrailMesh); + GDREGISTER_CLASS(RibbonTrailMesh); + GDREGISTER_CLASS(PointMesh); + GDREGISTER_VIRTUAL_CLASS(Material); + GDREGISTER_VIRTUAL_CLASS(BaseMaterial3D); + GDREGISTER_CLASS(StandardMaterial3D); + GDREGISTER_CLASS(ORMMaterial3D); SceneTree::add_idle_callback(BaseMaterial3D::flush_changes); BaseMaterial3D::init_shaders(); - ClassDB::register_class<MeshLibrary>(); + GDREGISTER_CLASS(MeshLibrary); OS::get_singleton()->yield(); //may take time to init - ClassDB::register_virtual_class<Shape3D>(); - ClassDB::register_class<RayShape3D>(); - ClassDB::register_class<SphereShape3D>(); - ClassDB::register_class<BoxShape3D>(); - ClassDB::register_class<CapsuleShape3D>(); - ClassDB::register_class<CylinderShape3D>(); - ClassDB::register_class<HeightMapShape3D>(); - ClassDB::register_class<WorldMarginShape3D>(); - ClassDB::register_class<ConvexPolygonShape3D>(); - ClassDB::register_class<ConcavePolygonShape3D>(); + GDREGISTER_VIRTUAL_CLASS(Shape3D); + GDREGISTER_CLASS(SeparationRayShape3D); + GDREGISTER_CLASS(SphereShape3D); + GDREGISTER_CLASS(BoxShape3D); + GDREGISTER_CLASS(CapsuleShape3D); + GDREGISTER_CLASS(CylinderShape3D); + GDREGISTER_CLASS(HeightMapShape3D); + GDREGISTER_CLASS(WorldBoundaryShape3D); + GDREGISTER_CLASS(ConvexPolygonShape3D); + GDREGISTER_CLASS(ConcavePolygonShape3D); + + ClassDB::register_class<SkeletonModificationStack3D>(); + ClassDB::register_class<SkeletonModification3D>(); + ClassDB::register_class<SkeletonModification3DLookAt>(); + ClassDB::register_class<SkeletonModification3DCCDIK>(); + ClassDB::register_class<SkeletonModification3DFABRIK>(); + ClassDB::register_class<SkeletonModification3DJiggle>(); + ClassDB::register_class<SkeletonModification3DTwoBoneIK>(); + ClassDB::register_class<SkeletonModification3DStackHolder>(); OS::get_singleton()->yield(); //may take time to init - ClassDB::register_class<VelocityTracker3D>(); + GDREGISTER_CLASS(VelocityTracker3D); #endif - ClassDB::register_class<PhysicsMaterial>(); - ClassDB::register_class<World3D>(); - ClassDB::register_class<Environment>(); - ClassDB::register_class<CameraEffects>(); - ClassDB::register_class<World2D>(); - ClassDB::register_virtual_class<Texture>(); - ClassDB::register_virtual_class<Texture2D>(); - ClassDB::register_class<Sky>(); - ClassDB::register_class<StreamTexture2D>(); - ClassDB::register_class<ImageTexture>(); - ClassDB::register_class<AtlasTexture>(); - ClassDB::register_class<MeshTexture>(); - ClassDB::register_class<CurveTexture>(); - ClassDB::register_class<Curve3Texture>(); - ClassDB::register_class<GradientTexture>(); - ClassDB::register_class<ProxyTexture>(); - ClassDB::register_class<AnimatedTexture>(); - ClassDB::register_class<CameraTexture>(); - ClassDB::register_virtual_class<TextureLayered>(); - ClassDB::register_virtual_class<ImageTextureLayered>(); - ClassDB::register_virtual_class<Texture3D>(); - ClassDB::register_class<ImageTexture3D>(); - ClassDB::register_class<StreamTexture3D>(); - ClassDB::register_class<Cubemap>(); - ClassDB::register_class<CubemapArray>(); - ClassDB::register_class<Texture2DArray>(); - ClassDB::register_virtual_class<StreamTextureLayered>(); - ClassDB::register_class<StreamCubemap>(); - ClassDB::register_class<StreamCubemapArray>(); - ClassDB::register_class<StreamTexture2DArray>(); - - ClassDB::register_class<Animation>(); - ClassDB::register_class<FontData>(); - ClassDB::register_class<Font>(); - ClassDB::register_class<Curve>(); - - ClassDB::register_class<TextFile>(); - ClassDB::register_class<TextLine>(); - ClassDB::register_class<TextParagraph>(); - - ClassDB::register_virtual_class<StyleBox>(); - ClassDB::register_class<StyleBoxEmpty>(); - ClassDB::register_class<StyleBoxTexture>(); - ClassDB::register_class<StyleBoxFlat>(); - ClassDB::register_class<StyleBoxLine>(); - ClassDB::register_class<Theme>(); - - ClassDB::register_class<PolygonPathFinder>(); - ClassDB::register_class<BitMap>(); - ClassDB::register_class<Gradient>(); + GDREGISTER_CLASS(PhysicsMaterial); + GDREGISTER_CLASS(World3D); + GDREGISTER_CLASS(Environment); + GDREGISTER_CLASS(CameraEffects); + GDREGISTER_CLASS(World2D); + GDREGISTER_VIRTUAL_CLASS(Texture); + GDREGISTER_VIRTUAL_CLASS(Texture2D); + GDREGISTER_CLASS(Sky); + GDREGISTER_CLASS(StreamTexture2D); + GDREGISTER_CLASS(ImageTexture); + GDREGISTER_CLASS(AtlasTexture); + GDREGISTER_CLASS(MeshTexture); + GDREGISTER_CLASS(CurveTexture); + GDREGISTER_CLASS(CurveXYZTexture); + GDREGISTER_CLASS(GradientTexture); + GDREGISTER_CLASS(ProxyTexture); + GDREGISTER_CLASS(AnimatedTexture); + GDREGISTER_CLASS(CameraTexture); + GDREGISTER_VIRTUAL_CLASS(TextureLayered); + GDREGISTER_VIRTUAL_CLASS(ImageTextureLayered); + GDREGISTER_VIRTUAL_CLASS(Texture3D); + GDREGISTER_CLASS(ImageTexture3D); + GDREGISTER_CLASS(StreamTexture3D); + GDREGISTER_CLASS(Cubemap); + GDREGISTER_CLASS(CubemapArray); + GDREGISTER_CLASS(Texture2DArray); + GDREGISTER_VIRTUAL_CLASS(StreamTextureLayered); + GDREGISTER_CLASS(StreamCubemap); + GDREGISTER_CLASS(StreamCubemapArray); + GDREGISTER_CLASS(StreamTexture2DArray); + + GDREGISTER_CLASS(Animation); + GDREGISTER_CLASS(FontData); + GDREGISTER_CLASS(Font); + GDREGISTER_CLASS(Curve); + + GDREGISTER_CLASS(TextLine); + GDREGISTER_CLASS(TextParagraph); + + GDREGISTER_VIRTUAL_CLASS(StyleBox); + GDREGISTER_CLASS(StyleBoxEmpty); + GDREGISTER_CLASS(StyleBoxTexture); + GDREGISTER_CLASS(StyleBoxFlat); + GDREGISTER_CLASS(StyleBoxLine); + GDREGISTER_CLASS(Theme); + + GDREGISTER_CLASS(PolygonPathFinder); + GDREGISTER_CLASS(BitMap); + GDREGISTER_CLASS(Gradient); OS::get_singleton()->yield(); //may take time to init - ClassDB::register_class<AudioStreamPlayer>(); - ClassDB::register_class<AudioStreamPlayer2D>(); + GDREGISTER_CLASS(AudioStreamPlayer); + GDREGISTER_CLASS(AudioStreamPlayer2D); #ifndef _3D_DISABLED - ClassDB::register_class<AudioStreamPlayer3D>(); + GDREGISTER_CLASS(AudioStreamPlayer3D); #endif - ClassDB::register_virtual_class<VideoStream>(); - ClassDB::register_class<AudioStreamSample>(); + GDREGISTER_VIRTUAL_CLASS(VideoStream); + GDREGISTER_CLASS(AudioStreamSample); OS::get_singleton()->yield(); //may take time to init - ClassDB::register_virtual_class<Shape2D>(); - ClassDB::register_class<LineShape2D>(); - ClassDB::register_class<SegmentShape2D>(); - ClassDB::register_class<RayShape2D>(); - ClassDB::register_class<CircleShape2D>(); - ClassDB::register_class<RectangleShape2D>(); - ClassDB::register_class<CapsuleShape2D>(); - ClassDB::register_class<ConvexPolygonShape2D>(); - ClassDB::register_class<ConcavePolygonShape2D>(); - ClassDB::register_class<Curve2D>(); - ClassDB::register_class<Path2D>(); - ClassDB::register_class<PathFollow2D>(); - - ClassDB::register_class<NavigationMesh>(); - ClassDB::register_class<NavigationPolygon>(); - ClassDB::register_class<NavigationRegion2D>(); - ClassDB::register_class<NavigationAgent2D>(); - ClassDB::register_class<NavigationObstacle2D>(); + GDREGISTER_VIRTUAL_CLASS(Shape2D); + GDREGISTER_CLASS(WorldBoundaryShape2D); + GDREGISTER_CLASS(SegmentShape2D); + GDREGISTER_CLASS(SeparationRayShape2D); + GDREGISTER_CLASS(CircleShape2D); + GDREGISTER_CLASS(RectangleShape2D); + GDREGISTER_CLASS(CapsuleShape2D); + GDREGISTER_CLASS(ConvexPolygonShape2D); + GDREGISTER_CLASS(ConcavePolygonShape2D); + GDREGISTER_CLASS(Curve2D); + GDREGISTER_CLASS(Path2D); + GDREGISTER_CLASS(PathFollow2D); + + GDREGISTER_CLASS(NavigationMesh); + GDREGISTER_CLASS(NavigationPolygon); + GDREGISTER_CLASS(NavigationRegion2D); + GDREGISTER_CLASS(NavigationAgent2D); + GDREGISTER_CLASS(NavigationObstacle2D); OS::get_singleton()->yield(); //may take time to init - ClassDB::register_virtual_class<SceneState>(); - ClassDB::register_class<PackedScene>(); + GDREGISTER_VIRTUAL_CLASS(SceneState); + GDREGISTER_CLASS(PackedScene); - ClassDB::register_class<SceneTree>(); - ClassDB::register_virtual_class<SceneTreeTimer>(); //sorry, you can't create it + GDREGISTER_CLASS(SceneTree); + GDREGISTER_VIRTUAL_CLASS(SceneTreeTimer); //sorry, you can't create it #ifndef DISABLE_DEPRECATED // Dropped in 4.0, near approximation. @@ -908,12 +914,12 @@ void register_scene_types() { ClassDB::add_compatibility_class("Generic6DOFJoint", "Generic6DOFJoint3D"); ClassDB::add_compatibility_class("HeightMapShape", "HeightMapShape3D"); ClassDB::add_compatibility_class("HingeJoint", "HingeJoint3D"); - ClassDB::add_compatibility_class("ImmediateGeometry", "ImmediateGeometry3D"); ClassDB::add_compatibility_class("Joint", "Joint3D"); ClassDB::add_compatibility_class("KinematicBody", "CharacterBody3D"); ClassDB::add_compatibility_class("KinematicBody2D", "CharacterBody2D"); ClassDB::add_compatibility_class("KinematicCollision", "KinematicCollision3D"); ClassDB::add_compatibility_class("Light", "Light3D"); + ClassDB::add_compatibility_class("LineShape2D", "WorldBoundaryShape2D"); ClassDB::add_compatibility_class("Listener", "Listener3D"); ClassDB::add_compatibility_class("MeshInstance", "MeshInstance3D"); ClassDB::add_compatibility_class("MultiMeshInstance", "MultiMeshInstance3D"); @@ -944,11 +950,12 @@ void register_scene_types() { ClassDB::add_compatibility_class("PhysicsServer", "PhysicsServer3D"); ClassDB::add_compatibility_class("PhysicsShapeQueryParameters", "PhysicsShapeQueryParameters3D"); ClassDB::add_compatibility_class("PinJoint", "PinJoint3D"); - ClassDB::add_compatibility_class("PlaneShape", "WorldMarginShape3D"); + ClassDB::add_compatibility_class("PlaneShape", "WorldBoundaryShape3D"); ClassDB::add_compatibility_class("ProceduralSky", "Sky"); ClassDB::add_compatibility_class("ProximityGroup", "ProximityGroup3D"); ClassDB::add_compatibility_class("RayCast", "RayCast3D"); - ClassDB::add_compatibility_class("RayShape", "RayShape3D"); + ClassDB::add_compatibility_class("RayShape", "SeparationRayShape3D"); + ClassDB::add_compatibility_class("RayShape2D", "SeparationRayShape2D"); ClassDB::add_compatibility_class("RemoteTransform", "RemoteTransform3D"); ClassDB::add_compatibility_class("RigidBody", "RigidBody3D"); ClassDB::add_compatibility_class("Shape", "Shape3D"); @@ -988,6 +995,7 @@ void register_scene_types() { ClassDB::add_compatibility_class("VisualShaderNodeVectorScalarSmoothStep", "VisualShaderNodeSmoothStep"); ClassDB::add_compatibility_class("VisualShaderNodeVectorScalarStep", "VisualShaderNodeStep"); ClassDB::add_compatibility_class("VisualShaderNodeScalarSwitch", "VisualShaderNodeSwitch"); + ClassDB::add_compatibility_class("VisualShaderNodeScalarTransformMult", "VisualShaderNodeTransformOp"); ClassDB::add_compatibility_class("World", "World3D"); ClassDB::add_compatibility_class("StreamTexture", "StreamTexture2D"); ClassDB::add_compatibility_class("Light2D", "PointLight2D"); @@ -999,12 +1007,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), ""); - GLOBAL_DEF_BASIC(vformat("layer_names/2d_physics/layer_%d", i), ""); - GLOBAL_DEF_BASIC(vformat("layer_names/2d_navigation/layer_%d", i), ""); - GLOBAL_DEF_BASIC(vformat("layer_names/3d_render/layer_%d", i), ""); - GLOBAL_DEF_BASIC(vformat("layer_names/3d_physics/layer_%d", i), ""); - GLOBAL_DEF_BASIC(vformat("layer_names/3d_navigation/layer_%d", 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), ""); + } + + 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), ""); } bool default_theme_hidpi = GLOBAL_DEF("gui/theme/use_hidpi", false); @@ -1050,14 +1061,6 @@ void unregister_scene_types() { SceneDebugger::deinitialize(); clear_default_theme(); - ResourceLoader::remove_resource_format_loader(resource_loader_font); - resource_loader_font.unref(); - -#ifndef DISABLE_DEPRECATED - ResourceLoader::remove_resource_format_loader(resource_loader_compat_font); - resource_loader_compat_font.unref(); -#endif /* DISABLE_DEPRECATED */ - ResourceLoader::remove_resource_format_loader(resource_loader_texture_layered); resource_loader_texture_layered.unref(); @@ -1084,6 +1087,10 @@ void unregister_scene_types() { BaseMaterial3D::finish_shaders(); #endif // _3D_DISABLED + PhysicalSkyMaterial::cleanup_shader(); + PanoramaSkyMaterial::cleanup_shader(); + ProceduralSkyMaterial::cleanup_shader(); + ParticlesMaterial::finish_shaders(); CanvasItemMaterial::finish_shaders(); ColorPicker::finish_shaders(); diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index 640ec50eb9..b4eec2530b 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -29,9 +29,9 @@ /*************************************************************************/ #include "animation.h" -#include "scene/scene_string_names.h" #include "core/math/geometry_3d.h" +#include "scene/scene_string_names.h" bool Animation::_set(const StringName &p_name, const Variant &p_value) { String name = p_name; @@ -77,17 +77,18 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { } else if (what == "keys" || what == "key_values") { if (track_get_type(track) == TYPE_TRANSFORM3D) { TransformTrack *tt = static_cast<TransformTrack *>(tracks[track]); - Vector<float> values = p_value; + Vector<real_t> values = p_value; int vcount = values.size(); - ERR_FAIL_COND_V(vcount % 12, false); // should be multiple of 11 + ERR_FAIL_COND_V(vcount % TRANSFORM_TRACK_SIZE, false); - const float *r = values.ptr(); + const real_t *r = values.ptr(); - tt->transforms.resize(vcount / 12); + int64_t count = vcount / TRANSFORM_TRACK_SIZE; + tt->transforms.resize(count); - for (int i = 0; i < (vcount / 12); i++) { + for (int i = 0; i < count; i++) { TKey<TransformKey> &tk = tt->transforms.write[i]; - const float *ofs = &r[i * 12]; + const real_t *ofs = &r[i * TRANSFORM_TRACK_SIZE]; tk.time = ofs[0]; tk.transition = ofs[1]; @@ -125,7 +126,7 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { vt->update_mode = UpdateMode(um); } - Vector<float> times = d["times"]; + Vector<real_t> times = d["times"]; Array values = d["values"]; ERR_FAIL_COND_V(times.size() != values.size(), false); @@ -133,7 +134,7 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { if (times.size()) { int valcount = times.size(); - const float *rt = times.ptr(); + const real_t *rt = times.ptr(); vt->values.resize(valcount); @@ -143,10 +144,10 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { } if (d.has("transitions")) { - Vector<float> transitions = d["transitions"]; + Vector<real_t> transitions = d["transitions"]; ERR_FAIL_COND_V(transitions.size() != valcount, false); - const float *rtr = transitions.ptr(); + const real_t *rtr = transitions.ptr(); for (int i = 0; i < valcount; i++) { vt->values.write[i].transition = rtr[i]; @@ -165,7 +166,7 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { ERR_FAIL_COND_V(!d.has("times"), false); ERR_FAIL_COND_V(!d.has("values"), false); - Vector<float> times = d["times"]; + Vector<real_t> times = d["times"]; Array values = d["values"]; ERR_FAIL_COND_V(times.size() != values.size(), false); @@ -173,17 +174,17 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { if (times.size()) { int valcount = times.size(); - const float *rt = times.ptr(); + const real_t *rt = times.ptr(); for (int i = 0; i < valcount; i++) { track_insert_key(track, rt[i], values[i]); } if (d.has("transitions")) { - Vector<float> transitions = d["transitions"]; + Vector<real_t> transitions = d["transitions"]; ERR_FAIL_COND_V(transitions.size() != valcount, false); - const float *rtr = transitions.ptr(); + const real_t *rtr = transitions.ptr(); for (int i = 0; i < valcount; i++) { track_set_key_transition(track, i, rtr[i]); @@ -196,16 +197,16 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { ERR_FAIL_COND_V(!d.has("times"), false); ERR_FAIL_COND_V(!d.has("points"), false); - Vector<float> times = d["times"]; - PackedFloat32Array values = d["points"]; + Vector<real_t> times = d["times"]; + Vector<real_t> values = d["points"]; ERR_FAIL_COND_V(times.size() * 5 != values.size(), false); if (times.size()) { int valcount = times.size(); - const float *rt = times.ptr(); - const float *rv = values.ptr(); + const real_t *rt = times.ptr(); + const real_t *rv = values.ptr(); bt->values.resize(valcount); @@ -227,7 +228,7 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { ERR_FAIL_COND_V(!d.has("times"), false); ERR_FAIL_COND_V(!d.has("clips"), false); - Vector<float> times = d["times"]; + Vector<real_t> times = d["times"]; Array clips = d["clips"]; ERR_FAIL_COND_V(clips.size() != times.size(), false); @@ -235,7 +236,7 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { if (times.size()) { int valcount = times.size(); - const float *rt = times.ptr(); + const real_t *rt = times.ptr(); ad->values.clear(); @@ -268,7 +269,7 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { ERR_FAIL_COND_V(!d.has("times"), false); ERR_FAIL_COND_V(!d.has("clips"), false); - Vector<float> times = d["times"]; + Vector<real_t> times = d["times"]; Vector<String> clips = d["clips"]; ERR_FAIL_COND_V(clips.size() != times.size(), false); @@ -276,7 +277,7 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { if (times.size()) { int valcount = times.size(); - const float *rt = times.ptr(); + const real_t *rt = times.ptr(); const String *rc = clips.ptr(); an->values.resize(valcount); @@ -352,9 +353,9 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { r_ret = track_is_enabled(track); } else if (what == "keys") { if (track_get_type(track) == TYPE_TRANSFORM3D) { - Vector<float> keys; + Vector<real_t> keys; int kk = track_get_key_count(track); - keys.resize(kk * 12); + keys.resize(kk * TRANSFORM_TRACK_SIZE); real_t *w = keys.ptrw(); @@ -389,8 +390,8 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { Dictionary d; - Vector<float> key_times; - Vector<float> key_transitions; + Vector<real_t> key_times; + Vector<real_t> key_transitions; Array key_values; int kk = vt->values.size(); @@ -399,8 +400,8 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { key_transitions.resize(kk); key_values.resize(kk); - float *wti = key_times.ptrw(); - float *wtr = key_transitions.ptrw(); + real_t *wti = key_times.ptrw(); + real_t *wtr = key_transitions.ptrw(); int idx = 0; @@ -427,8 +428,8 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { } else if (track_get_type(track) == TYPE_METHOD) { Dictionary d; - Vector<float> key_times; - Vector<float> key_transitions; + Vector<real_t> key_times; + Vector<real_t> key_transitions; Array key_values; int kk = track_get_key_count(track); @@ -437,8 +438,8 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { key_transitions.resize(kk); key_values.resize(kk); - float *wti = key_times.ptrw(); - float *wtr = key_transitions.ptrw(); + real_t *wti = key_times.ptrw(); + real_t *wtr = key_transitions.ptrw(); int idx = 0; for (int i = 0; i < track_get_key_count(track); i++) { @@ -463,16 +464,16 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { Dictionary d; - Vector<float> key_times; - Vector<float> key_points; + Vector<real_t> key_times; + Vector<real_t> key_points; int kk = bt->values.size(); key_times.resize(kk); key_points.resize(kk * 5); - float *wti = key_times.ptrw(); - float *wpo = key_points.ptrw(); + real_t *wti = key_times.ptrw(); + real_t *wpo = key_points.ptrw(); int idx = 0; @@ -499,14 +500,14 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { Dictionary d; - Vector<float> key_times; + Vector<real_t> key_times; Array clips; int kk = ad->values.size(); key_times.resize(kk); - float *wti = key_times.ptrw(); + real_t *wti = key_times.ptrw(); int idx = 0; @@ -533,7 +534,7 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { Dictionary d; - Vector<float> key_times; + Vector<real_t> key_times; Vector<String> clips; int kk = an->values.size(); @@ -541,7 +542,7 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { key_times.resize(kk); clips.resize(kk); - float *wti = key_times.ptrw(); + real_t *wti = key_times.ptrw(); String *wcl = clips.ptrw(); const TKey<StringName> *vls = an->values.ptr(); @@ -722,7 +723,7 @@ bool Animation::track_get_interpolation_loop_wrap(int p_track) const { // transform /* template<class T> -int Animation::_insert_pos(float p_time, T& p_keys) { +int Animation::_insert_pos(double p_time, T& p_keys) { // simple, linear time inset that should be fast enough in reality. int idx=p_keys.size(); @@ -745,12 +746,12 @@ int Animation::_insert_pos(float p_time, T& p_keys) { */ template <class T, class V> -int Animation::_insert(float p_time, T &p_keys, const V &p_value) { +int Animation::_insert(double p_time, T &p_keys, const V &p_value) { int idx = p_keys.size(); while (true) { // Condition for replacement. - if (idx > 0 && Math::is_equal_approx(p_keys[idx - 1].time, p_time)) { + if (idx > 0 && Math::is_equal_approx((double)p_keys[idx - 1].time, p_time)) { float transition = p_keys[idx - 1].transition; p_keys.write[idx - 1] = p_value; p_keys.write[idx - 1].transition = transition; @@ -794,7 +795,7 @@ Error Animation::transform_track_get_key(int p_track, int p_key, Vector3 *r_loc, return OK; } -int Animation::transform_track_insert_key(int p_track, float p_time, const Vector3 &p_loc, const Quaternion &p_rot, const Vector3 &p_scale) { +int Animation::transform_track_insert_key(int p_track, double p_time, const Vector3 &p_loc, const Quaternion &p_rot, const Vector3 &p_scale) { ERR_FAIL_INDEX_V(p_track, tracks.size(), -1); Track *t = tracks[p_track]; ERR_FAIL_COND_V(t->type != TYPE_TRANSFORM3D, -1); @@ -812,7 +813,7 @@ int Animation::transform_track_insert_key(int p_track, float p_time, const Vecto return ret; } -void Animation::track_remove_key_at_time(int p_track, float p_time) { +void Animation::track_remove_key_at_time(int p_track, double p_time) { int idx = track_find_key(p_track, p_time, true); ERR_FAIL_COND(idx < 0); track_remove_key(p_track, idx); @@ -864,7 +865,7 @@ void Animation::track_remove_key(int p_track, int p_idx) { emit_changed(); } -int Animation::track_find_key(int p_track, float p_time, bool p_exact) const { +int Animation::track_find_key(int p_track, double p_time, bool p_exact) const { ERR_FAIL_INDEX_V(p_track, tracks.size(), -1); Track *t = tracks[p_track]; @@ -946,7 +947,7 @@ int Animation::track_find_key(int p_track, float p_time, bool p_exact) const { return -1; } -void Animation::track_insert_key(int p_track, float p_time, const Variant &p_key, float p_transition) { +void Animation::track_insert_key(int p_track, double p_time, const Variant &p_key, real_t p_transition) { ERR_FAIL_INDEX(p_track, tracks.size()); Track *t = tracks[p_track]; @@ -1151,7 +1152,7 @@ Variant Animation::track_get_key_value(int p_track, int p_key_idx) const { ERR_FAIL_V(Variant()); } -float Animation::track_get_key_time(int p_track, int p_key_idx) const { +double Animation::track_get_key_time(int p_track, int p_key_idx) const { ERR_FAIL_INDEX_V(p_track, tracks.size(), -1); Track *t = tracks[p_track]; @@ -1196,7 +1197,7 @@ float Animation::track_get_key_time(int p_track, int p_key_idx) const { ERR_FAIL_V(-1); } -void Animation::track_set_key_time(int p_track, int p_key_idx, float p_time) { +void Animation::track_set_key_time(int p_track, int p_key_idx, double p_time) { ERR_FAIL_INDEX(p_track, tracks.size()); Track *t = tracks[p_track]; @@ -1260,7 +1261,7 @@ void Animation::track_set_key_time(int p_track, int p_key_idx, float p_time) { ERR_FAIL(); } -float Animation::track_get_key_transition(int p_track, int p_key_idx) const { +real_t Animation::track_get_key_transition(int p_track, int p_key_idx) const { ERR_FAIL_INDEX_V(p_track, tracks.size(), -1); Track *t = tracks[p_track]; @@ -1379,7 +1380,7 @@ void Animation::track_set_key_value(int p_track, int p_key_idx, const Variant &p emit_changed(); } -void Animation::track_set_key_transition(int p_track, int p_key_idx, float p_transition) { +void Animation::track_set_key_transition(int p_track, int p_key_idx, real_t p_transition) { ERR_FAIL_INDEX(p_track, tracks.size()); Track *t = tracks[p_track]; @@ -1412,7 +1413,7 @@ void Animation::track_set_key_transition(int p_track, int p_key_idx, float p_tra } template <class K> -int Animation::_find(const Vector<K> &p_keys, float p_time) const { +int Animation::_find(const Vector<K> &p_keys, double p_time) const { int len = p_keys.size(); if (len == 0) { return -2; @@ -1433,7 +1434,7 @@ int Animation::_find(const Vector<K> &p_keys, float p_time) const { while (low <= high) { middle = (low + high) / 2; - if (Math::is_equal_approx(p_time, keys[middle].time)) { //match + if (Math::is_equal_approx(p_time, (double)keys[middle].time)) { //match return middle; } else if (p_time < keys[middle].time) { high = middle - 1; //search low end of array @@ -1449,7 +1450,7 @@ int Animation::_find(const Vector<K> &p_keys, float p_time) const { return middle; } -Animation::TransformKey Animation::_interpolate(const Animation::TransformKey &p_a, const Animation::TransformKey &p_b, float p_c) const { +Animation::TransformKey Animation::_interpolate(const Animation::TransformKey &p_a, const Animation::TransformKey &p_b, real_t p_c) const { TransformKey ret; ret.loc = _interpolate(p_a.loc, p_b.loc, p_c); ret.rot = _interpolate(p_a.rot, p_b.rot, p_c); @@ -1458,25 +1459,25 @@ Animation::TransformKey Animation::_interpolate(const Animation::TransformKey &p return ret; } -Vector3 Animation::_interpolate(const Vector3 &p_a, const Vector3 &p_b, float p_c) const { +Vector3 Animation::_interpolate(const Vector3 &p_a, const Vector3 &p_b, real_t p_c) const { return p_a.lerp(p_b, p_c); } -Quaternion Animation::_interpolate(const Quaternion &p_a, const Quaternion &p_b, float p_c) const { +Quaternion Animation::_interpolate(const Quaternion &p_a, const Quaternion &p_b, real_t p_c) const { return p_a.slerp(p_b, p_c); } -Variant Animation::_interpolate(const Variant &p_a, const Variant &p_b, float p_c) const { +Variant Animation::_interpolate(const Variant &p_a, const Variant &p_b, real_t p_c) const { Variant dst; Variant::interpolate(p_a, p_b, p_c, dst); return dst; } -float Animation::_interpolate(const float &p_a, const float &p_b, float p_c) const { +real_t Animation::_interpolate(const real_t &p_a, const real_t &p_b, real_t p_c) const { return p_a * (1.0 - p_c) + p_b * p_c; } -Animation::TransformKey Animation::_cubic_interpolate(const Animation::TransformKey &p_pre_a, const Animation::TransformKey &p_a, const Animation::TransformKey &p_b, const Animation::TransformKey &p_post_b, float p_c) const { +Animation::TransformKey Animation::_cubic_interpolate(const Animation::TransformKey &p_pre_a, const Animation::TransformKey &p_a, const Animation::TransformKey &p_b, const Animation::TransformKey &p_post_b, real_t p_c) const { Animation::TransformKey tk; tk.loc = p_a.loc.cubic_interpolate(p_b.loc, p_pre_a.loc, p_post_b.loc, p_c); @@ -1486,15 +1487,15 @@ Animation::TransformKey Animation::_cubic_interpolate(const Animation::Transform return tk; } -Vector3 Animation::_cubic_interpolate(const Vector3 &p_pre_a, const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_post_b, float p_c) const { +Vector3 Animation::_cubic_interpolate(const Vector3 &p_pre_a, const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_post_b, real_t p_c) const { return p_a.cubic_interpolate(p_b, p_pre_a, p_post_b, p_c); } -Quaternion Animation::_cubic_interpolate(const Quaternion &p_pre_a, const Quaternion &p_a, const Quaternion &p_b, const Quaternion &p_post_b, float p_c) const { +Quaternion Animation::_cubic_interpolate(const Quaternion &p_pre_a, const Quaternion &p_a, const Quaternion &p_b, const Quaternion &p_post_b, real_t p_c) const { return p_a.cubic_slerp(p_b, p_pre_a, p_post_b, p_c); } -Variant Animation::_cubic_interpolate(const Variant &p_pre_a, const Variant &p_a, const Variant &p_b, const Variant &p_post_b, float p_c) const { +Variant Animation::_cubic_interpolate(const Variant &p_pre_a, const Variant &p_a, const Variant &p_b, const Variant &p_post_b, real_t p_c) const { Variant::Type type_a = p_a.get_type(); Variant::Type type_b = p_b.get_type(); Variant::Type type_pa = p_pre_a.get_type(); @@ -1515,9 +1516,9 @@ Variant Animation::_cubic_interpolate(const Variant &p_pre_a, const Variant &p_a real_t p2 = p_b; real_t p3 = p_post_b; - float t = p_c; - float t2 = t * t; - float t3 = t2 * t; + real_t t = p_c; + real_t t2 = t * t; + real_t t3 = t2 * t; return 0.5f * ((p1 * 2.0f) + (-p0 + p2) * t + @@ -1579,12 +1580,12 @@ Variant Animation::_cubic_interpolate(const Variant &p_pre_a, const Variant &p_a } } -float Animation::_cubic_interpolate(const float &p_pre_a, const float &p_a, const float &p_b, const float &p_post_b, float p_c) const { +real_t Animation::_cubic_interpolate(const real_t &p_pre_a, const real_t &p_a, const real_t &p_b, const real_t &p_post_b, real_t p_c) const { return _interpolate(p_a, p_b, p_c); } template <class T> -T Animation::_interpolate(const Vector<TKey<T>> &p_keys, float p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok) const { +T Animation::_interpolate(const Vector<TKey<T>> &p_keys, double p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok) const { int len = _find(p_keys, length) + 1; // try to find last key (there may be more past the end) if (len <= 0) { @@ -1608,7 +1609,7 @@ T Animation::_interpolate(const Vector<TKey<T>> &p_keys, float p_time, Interpola bool result = true; int next = 0; - float c = 0.0; + real_t c = 0.0; // prepare for all cases of interpolation if (loop && p_loop_wrap) { @@ -1616,8 +1617,8 @@ T Animation::_interpolate(const Vector<TKey<T>> &p_keys, float p_time, Interpola if (idx >= 0) { if ((idx + 1) < len) { next = idx + 1; - float delta = p_keys[next].time - p_keys[idx].time; - float from = p_time - p_keys[idx].time; + real_t delta = p_keys[next].time - p_keys[idx].time; + real_t from = p_time - p_keys[idx].time; if (Math::is_zero_approx(delta)) { c = 0; @@ -1627,8 +1628,8 @@ T Animation::_interpolate(const Vector<TKey<T>> &p_keys, float p_time, Interpola } else { next = 0; - float delta = (length - p_keys[idx].time) + p_keys[next].time; - float from = p_time - p_keys[idx].time; + real_t delta = (length - p_keys[idx].time) + p_keys[next].time; + real_t from = p_time - p_keys[idx].time; if (Math::is_zero_approx(delta)) { c = 0; @@ -1641,12 +1642,12 @@ T Animation::_interpolate(const Vector<TKey<T>> &p_keys, float p_time, Interpola // on loop, behind first key idx = len - 1; next = 0; - float endtime = (length - p_keys[idx].time); + real_t endtime = (length - p_keys[idx].time); if (endtime < 0) { // may be keys past the end endtime = 0; } - float delta = endtime + p_keys[next].time; - float from = endtime + p_time; + real_t delta = endtime + p_keys[next].time; + real_t from = endtime + p_time; if (Math::is_zero_approx(delta)) { c = 0; @@ -1660,8 +1661,8 @@ T Animation::_interpolate(const Vector<TKey<T>> &p_keys, float p_time, Interpola if (idx >= 0) { if ((idx + 1) < len) { next = idx + 1; - float delta = p_keys[next].time - p_keys[idx].time; - float from = p_time - p_keys[idx].time; + real_t delta = p_keys[next].time - p_keys[idx].time; + real_t from = p_time - p_keys[idx].time; if (Math::is_zero_approx(delta)) { c = 0; @@ -1690,7 +1691,7 @@ T Animation::_interpolate(const Vector<TKey<T>> &p_keys, float p_time, Interpola return T(); } - float tr = p_keys[idx].transition; + real_t tr = p_keys[idx].transition; if (tr == 0 || idx == next) { // don't interpolate if not needed @@ -1728,7 +1729,7 @@ T Animation::_interpolate(const Vector<TKey<T>> &p_keys, float p_time, Interpola // do a barrel roll } -Error Animation::transform_track_interpolate(int p_track, float p_time, Vector3 *r_loc, Quaternion *r_rot, Vector3 *r_scale) const { +Error Animation::transform_track_interpolate(int p_track, double p_time, Vector3 *r_loc, Quaternion *r_rot, Vector3 *r_scale) const { ERR_FAIL_INDEX_V(p_track, tracks.size(), ERR_INVALID_PARAMETER); Track *t = tracks[p_track]; ERR_FAIL_COND_V(t->type != TYPE_TRANSFORM3D, ERR_INVALID_PARAMETER); @@ -1758,7 +1759,7 @@ Error Animation::transform_track_interpolate(int p_track, float p_time, Vector3 return OK; } -Variant Animation::value_track_interpolate(int p_track, float p_time) const { +Variant Animation::value_track_interpolate(int p_track, double p_time) const { ERR_FAIL_INDEX_V(p_track, tracks.size(), 0); Track *t = tracks[p_track]; ERR_FAIL_COND_V(t->type != TYPE_VALUE, Variant()); @@ -1775,7 +1776,7 @@ Variant Animation::value_track_interpolate(int p_track, float p_time) const { return Variant(); } -void Animation::_value_track_get_key_indices_in_range(const ValueTrack *vt, float from_time, float to_time, List<int> *p_indices) const { +void Animation::_value_track_get_key_indices_in_range(const ValueTrack *vt, double from_time, double to_time, List<int> *p_indices) const { if (from_time != length && to_time == length) { to_time = length * 1.001; //include a little more if at the end } @@ -1812,15 +1813,15 @@ void Animation::_value_track_get_key_indices_in_range(const ValueTrack *vt, floa } } -void Animation::value_track_get_key_indices(int p_track, float p_time, float p_delta, List<int> *p_indices) const { +void Animation::value_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices) const { ERR_FAIL_INDEX(p_track, tracks.size()); Track *t = tracks[p_track]; ERR_FAIL_COND(t->type != TYPE_VALUE); ValueTrack *vt = static_cast<ValueTrack *>(t); - float from_time = p_time - p_delta; - float to_time = p_time; + double from_time = p_time - p_delta; + double to_time = p_time; if (from_time > to_time) { SWAP(from_time, to_time); @@ -1875,7 +1876,7 @@ Animation::UpdateMode Animation::value_track_get_update_mode(int p_track) const } template <class T> -void Animation::_track_get_key_indices_in_range(const Vector<T> &p_array, float from_time, float to_time, List<int> *p_indices) const { +void Animation::_track_get_key_indices_in_range(const Vector<T> &p_array, double from_time, double to_time, List<int> *p_indices) const { if (from_time != length && to_time == length) { to_time = length * 1.01; //include a little more if at the end } @@ -1908,12 +1909,12 @@ void Animation::_track_get_key_indices_in_range(const Vector<T> &p_array, float } } -void Animation::track_get_key_indices_in_range(int p_track, float p_time, float p_delta, List<int> *p_indices) const { +void Animation::track_get_key_indices_in_range(int p_track, double p_time, double p_delta, List<int> *p_indices) const { ERR_FAIL_INDEX(p_track, tracks.size()); const Track *t = tracks[p_track]; - float from_time = p_time - p_delta; - float to_time = p_time; + double from_time = p_time - p_delta; + double to_time = p_time; if (from_time > to_time) { SWAP(from_time, to_time); @@ -2021,7 +2022,7 @@ void Animation::track_get_key_indices_in_range(int p_track, float p_time, float } } -void Animation::_method_track_get_key_indices_in_range(const MethodTrack *mt, float from_time, float to_time, List<int> *p_indices) const { +void Animation::_method_track_get_key_indices_in_range(const MethodTrack *mt, double from_time, double to_time, List<int> *p_indices) const { if (from_time != length && to_time == length) { to_time = length * 1.01; //include a little more if at the end } @@ -2054,15 +2055,15 @@ void Animation::_method_track_get_key_indices_in_range(const MethodTrack *mt, fl } } -void Animation::method_track_get_key_indices(int p_track, float p_time, float p_delta, List<int> *p_indices) const { +void Animation::method_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices) const { ERR_FAIL_INDEX(p_track, tracks.size()); Track *t = tracks[p_track]; ERR_FAIL_COND(t->type != TYPE_METHOD); MethodTrack *mt = static_cast<MethodTrack *>(t); - float from_time = p_time - p_delta; - float to_time = p_time; + double from_time = p_time - p_delta; + double to_time = p_time; if (from_time > to_time) { SWAP(from_time, to_time); @@ -2128,7 +2129,7 @@ StringName Animation::method_track_get_name(int p_track, int p_key_idx) const { return pm->methods[p_key_idx].method; } -int Animation::bezier_track_insert_key(int p_track, float p_time, float p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle) { +int Animation::bezier_track_insert_key(int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle) { ERR_FAIL_INDEX_V(p_track, tracks.size(), -1); Track *t = tracks[p_track]; ERR_FAIL_COND_V(t->type != TYPE_BEZIER, -1); @@ -2154,7 +2155,7 @@ int Animation::bezier_track_insert_key(int p_track, float p_time, float p_value, return key; } -void Animation::bezier_track_set_key_value(int p_track, int p_index, float p_value) { +void Animation::bezier_track_set_key_value(int p_track, int p_index, real_t p_value) { ERR_FAIL_INDEX(p_track, tracks.size()); Track *t = tracks[p_track]; ERR_FAIL_COND(t->type != TYPE_BEZIER); @@ -2199,7 +2200,7 @@ void Animation::bezier_track_set_key_out_handle(int p_track, int p_index, const emit_changed(); } -float Animation::bezier_track_get_key_value(int p_track, int p_index) const { +real_t Animation::bezier_track_get_key_value(int p_track, int p_index) const { ERR_FAIL_INDEX_V(p_track, tracks.size(), 0); Track *t = tracks[p_track]; ERR_FAIL_COND_V(t->type != TYPE_BEZIER, 0); @@ -2246,7 +2247,7 @@ static _FORCE_INLINE_ Vector2 _bezier_interp(real_t t, const Vector2 &start, con return start * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3; } -float Animation::bezier_track_interpolate(int p_track, float p_time) const { +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); Track *track = tracks[p_track]; @@ -2277,14 +2278,14 @@ float Animation::bezier_track_interpolate(int p_track, float p_time) const { return bt->values[bt->values.size() - 1].value.value; } - float t = p_time - bt->values[idx].time; + double t = p_time - bt->values[idx].time; int iterations = 10; - float duration = bt->values[idx + 1].time - bt->values[idx].time; // time duration between our two keyframes - float low = 0.0; // 0% of the current animation segment - float high = 1.0; // 100% of the current animation segment - float middle; + real_t duration = bt->values[idx + 1].time - bt->values[idx].time; // time duration between our two keyframes + real_t low = 0.0; // 0% of the current animation segment + real_t high = 1.0; // 100% of the current animation segment + real_t middle; Vector2 start(0, bt->values[idx].value.value); Vector2 start_out = start + bt->values[idx].value.out_handle; @@ -2307,12 +2308,12 @@ float Animation::bezier_track_interpolate(int p_track, float 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); - float c = (t - low_pos.x) / (high_pos.x - low_pos.x); + 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, float p_time, const RES &p_stream, float p_start_offset, float p_end_offset) { +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) { ERR_FAIL_INDEX_V(p_track, tracks.size(), -1); Track *t = tracks[p_track]; ERR_FAIL_COND_V(t->type != TYPE_AUDIO, -1); @@ -2352,7 +2353,7 @@ void Animation::audio_track_set_key_stream(int p_track, int p_key, const RES &p_ emit_changed(); } -void Animation::audio_track_set_key_start_offset(int p_track, int p_key, float p_offset) { +void Animation::audio_track_set_key_start_offset(int p_track, int p_key, real_t p_offset) { ERR_FAIL_INDEX(p_track, tracks.size()); Track *t = tracks[p_track]; ERR_FAIL_COND(t->type != TYPE_AUDIO); @@ -2370,7 +2371,7 @@ void Animation::audio_track_set_key_start_offset(int p_track, int p_key, float p emit_changed(); } -void Animation::audio_track_set_key_end_offset(int p_track, int p_key, float p_offset) { +void Animation::audio_track_set_key_end_offset(int p_track, int p_key, real_t p_offset) { ERR_FAIL_INDEX(p_track, tracks.size()); Track *t = tracks[p_track]; ERR_FAIL_COND(t->type != TYPE_AUDIO); @@ -2400,7 +2401,7 @@ RES Animation::audio_track_get_key_stream(int p_track, int p_key) const { return at->values[p_key].value.stream; } -float Animation::audio_track_get_key_start_offset(int p_track, int p_key) const { +real_t Animation::audio_track_get_key_start_offset(int p_track, int p_key) const { ERR_FAIL_INDEX_V(p_track, tracks.size(), 0); const Track *t = tracks[p_track]; ERR_FAIL_COND_V(t->type != TYPE_AUDIO, 0); @@ -2412,7 +2413,7 @@ float Animation::audio_track_get_key_start_offset(int p_track, int p_key) const return at->values[p_key].value.start_offset; } -float Animation::audio_track_get_key_end_offset(int p_track, int p_key) const { +real_t Animation::audio_track_get_key_end_offset(int p_track, int p_key) const { ERR_FAIL_INDEX_V(p_track, tracks.size(), 0); const Track *t = tracks[p_track]; ERR_FAIL_COND_V(t->type != TYPE_AUDIO, 0); @@ -2426,7 +2427,7 @@ float Animation::audio_track_get_key_end_offset(int p_track, int p_key) const { // -int Animation::animation_track_insert_key(int p_track, float p_time, const StringName &p_animation) { +int Animation::animation_track_insert_key(int p_track, double p_time, const StringName &p_animation) { ERR_FAIL_INDEX_V(p_track, tracks.size(), -1); Track *t = tracks[p_track]; ERR_FAIL_COND_V(t->type != TYPE_ANIMATION, -1); @@ -2470,7 +2471,7 @@ StringName Animation::animation_track_get_key_animation(int p_track, int p_key) return at->values[p_key].value; } -void Animation::set_length(float p_length) { +void Animation::set_length(real_t p_length) { if (p_length < ANIM_MIN_LENGTH) { p_length = ANIM_MIN_LENGTH; } @@ -2478,7 +2479,7 @@ void Animation::set_length(float p_length) { emit_changed(); } -float Animation::get_length() const { +real_t Animation::get_length() const { return length; } @@ -2558,12 +2559,12 @@ void Animation::track_swap(int p_track, int p_with_track) { emit_signal(SceneStringNames::get_singleton()->tracks_changed); } -void Animation::set_step(float p_step) { +void Animation::set_step(real_t p_step) { step = p_step; emit_changed(); } -float Animation::get_step() const { +real_t Animation::get_step() const { return step; } @@ -2708,7 +2709,7 @@ void Animation::clear() { emit_signal(SceneStringNames::get_singleton()->tracks_changed); } -bool Animation::_transform_track_optimize_key(const TKey<TransformKey> &t0, const TKey<TransformKey> &t1, const TKey<TransformKey> &t2, float p_alowed_linear_err, float p_alowed_angular_err, float p_max_optimizable_angle, const Vector3 &p_norm) { +bool Animation::_transform_track_optimize_key(const TKey<TransformKey> &t0, const TKey<TransformKey> &t1, const TKey<TransformKey> &t2, real_t p_alowed_linear_err, real_t p_alowed_angular_err, real_t p_max_optimizable_angle, const Vector3 &p_norm) { real_t c = (t1.time - t0.time) / (t2.time - t0.time); real_t t[3] = { -1, -1, -1 }; @@ -2727,9 +2728,9 @@ bool Animation::_transform_track_optimize_key(const TKey<TransformKey> &t0, cons } else { Vector3 pd = (v2 - v0); - float d0 = pd.dot(v0); - float d1 = pd.dot(v1); - float d2 = pd.dot(v2); + real_t d0 = pd.dot(v0); + real_t d1 = pd.dot(v1); + real_t d2 = pd.dot(v2); if (d1 < d0 || d1 > d2) { return false; } @@ -2817,9 +2818,9 @@ bool Animation::_transform_track_optimize_key(const TKey<TransformKey> &t0, cons } else { Vector3 pd = (v2 - v0); - float d0 = pd.dot(v0); - float d1 = pd.dot(v1); - float d2 = pd.dot(v2); + real_t d0 = pd.dot(v0); + real_t d1 = pd.dot(v1); + real_t d2 = pd.dot(v2); if (d1 < d0 || d1 > d2) { return false; //beyond segment range } @@ -2875,7 +2876,7 @@ bool Animation::_transform_track_optimize_key(const TKey<TransformKey> &t0, cons return erase; } -void Animation::_transform_track_optimize(int p_idx, float p_allowed_linear_err, float p_allowed_angular_err, float p_max_optimizable_angle) { +void Animation::_transform_track_optimize(int p_idx, real_t p_allowed_linear_err, real_t p_allowed_angular_err, real_t p_max_optimizable_angle) { ERR_FAIL_INDEX(p_idx, tracks.size()); ERR_FAIL_COND(tracks[p_idx]->type != TYPE_TRANSFORM3D); TransformTrack *tt = static_cast<TransformTrack *>(tracks[p_idx]); @@ -2915,7 +2916,7 @@ void Animation::_transform_track_optimize(int p_idx, float p_allowed_linear_err, } } -void Animation::optimize(float p_allowed_linear_err, float p_allowed_angular_err, float p_max_optimizable_angle) { +void Animation::optimize(real_t p_allowed_linear_err, real_t p_allowed_angular_err, real_t p_max_optimizable_angle) { for (int i = 0; i < tracks.size(); i++) { if (tracks[i]->type == TYPE_TRANSFORM3D) { _transform_track_optimize(i, p_allowed_linear_err, p_allowed_angular_err, p_max_optimizable_angle); diff --git a/scene/resources/animation.h b/scene/resources/animation.h index 1484007333..9a410bd566 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -76,8 +76,8 @@ private: }; struct Key { - float transition = 1.0; - float time = 0.0; // time in secs + real_t transition = 1.0; + double time = 0.0; // time in secs }; // transform key holds either Vector3 or Quaternion @@ -92,6 +92,9 @@ private: Vector3 scale; }; + // Not necessarily the same size as Transform3D. The amount of numbers in Animation::Key and TransformKey. + const int32_t TRANSFORM_TRACK_SIZE = 12; + /* TRANSFORM TRACK */ struct TransformTrack : public Track { @@ -129,7 +132,7 @@ private: struct BezierKey { Vector2 in_handle; //relative (x always <0) Vector2 out_handle; //relative (x always >0) - float value = 0.0; + real_t value = 0.0; }; struct BezierTrack : public Track { @@ -144,8 +147,8 @@ private: struct AudioKey { RES stream; - float start_offset = 0.0; //offset from start - float end_offset = 0.0; //offset from end, if 0 then full length or infinite + 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() { } }; @@ -172,46 +175,46 @@ private: /* template<class T> - int _insert_pos(float p_time, T& p_keys);*/ + int _insert_pos(double p_time, T& p_keys);*/ template <class T> void _clear(T &p_keys); template <class T, class V> - int _insert(float p_time, T &p_keys, const V &p_value); + int _insert(double p_time, T &p_keys, const V &p_value); template <class K> - inline int _find(const Vector<K> &p_keys, float p_time) const; + inline int _find(const Vector<K> &p_keys, double p_time) const; - _FORCE_INLINE_ Animation::TransformKey _interpolate(const Animation::TransformKey &p_a, const Animation::TransformKey &p_b, float p_c) const; + _FORCE_INLINE_ Animation::TransformKey _interpolate(const Animation::TransformKey &p_a, const Animation::TransformKey &p_b, real_t p_c) const; - _FORCE_INLINE_ Vector3 _interpolate(const Vector3 &p_a, const Vector3 &p_b, float p_c) const; - _FORCE_INLINE_ Quaternion _interpolate(const Quaternion &p_a, const Quaternion &p_b, float p_c) const; - _FORCE_INLINE_ Variant _interpolate(const Variant &p_a, const Variant &p_b, float p_c) const; - _FORCE_INLINE_ float _interpolate(const float &p_a, const float &p_b, float p_c) const; + _FORCE_INLINE_ Vector3 _interpolate(const Vector3 &p_a, const Vector3 &p_b, real_t p_c) const; + _FORCE_INLINE_ Quaternion _interpolate(const Quaternion &p_a, const Quaternion &p_b, real_t p_c) const; + _FORCE_INLINE_ Variant _interpolate(const Variant &p_a, const Variant &p_b, real_t p_c) const; + _FORCE_INLINE_ real_t _interpolate(const real_t &p_a, const real_t &p_b, real_t p_c) const; - _FORCE_INLINE_ Animation::TransformKey _cubic_interpolate(const Animation::TransformKey &p_pre_a, const Animation::TransformKey &p_a, const Animation::TransformKey &p_b, const Animation::TransformKey &p_post_b, float p_c) const; - _FORCE_INLINE_ Vector3 _cubic_interpolate(const Vector3 &p_pre_a, const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_post_b, float p_c) const; - _FORCE_INLINE_ Quaternion _cubic_interpolate(const Quaternion &p_pre_a, const Quaternion &p_a, const Quaternion &p_b, const Quaternion &p_post_b, float p_c) const; - _FORCE_INLINE_ Variant _cubic_interpolate(const Variant &p_pre_a, const Variant &p_a, const Variant &p_b, const Variant &p_post_b, float p_c) const; - _FORCE_INLINE_ float _cubic_interpolate(const float &p_pre_a, const float &p_a, const float &p_b, const float &p_post_b, float p_c) const; + _FORCE_INLINE_ Animation::TransformKey _cubic_interpolate(const Animation::TransformKey &p_pre_a, const Animation::TransformKey &p_a, const Animation::TransformKey &p_b, const Animation::TransformKey &p_post_b, real_t p_c) const; + _FORCE_INLINE_ Vector3 _cubic_interpolate(const Vector3 &p_pre_a, const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_post_b, real_t p_c) const; + _FORCE_INLINE_ Quaternion _cubic_interpolate(const Quaternion &p_pre_a, const Quaternion &p_a, const Quaternion &p_b, const Quaternion &p_post_b, real_t p_c) const; + _FORCE_INLINE_ Variant _cubic_interpolate(const Variant &p_pre_a, const Variant &p_a, const Variant &p_b, const Variant &p_post_b, real_t p_c) const; + _FORCE_INLINE_ real_t _cubic_interpolate(const real_t &p_pre_a, const real_t &p_a, const real_t &p_b, const real_t &p_post_b, real_t p_c) const; template <class T> - _FORCE_INLINE_ T _interpolate(const Vector<TKey<T>> &p_keys, float p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok) const; + _FORCE_INLINE_ T _interpolate(const Vector<TKey<T>> &p_keys, double p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok) const; template <class T> - _FORCE_INLINE_ void _track_get_key_indices_in_range(const Vector<T> &p_array, float from_time, float to_time, List<int> *p_indices) const; + _FORCE_INLINE_ void _track_get_key_indices_in_range(const Vector<T> &p_array, double from_time, double to_time, List<int> *p_indices) const; - _FORCE_INLINE_ void _value_track_get_key_indices_in_range(const ValueTrack *vt, float from_time, float to_time, List<int> *p_indices) const; - _FORCE_INLINE_ void _method_track_get_key_indices_in_range(const MethodTrack *mt, float from_time, float to_time, List<int> *p_indices) const; + _FORCE_INLINE_ void _value_track_get_key_indices_in_range(const ValueTrack *vt, double from_time, double to_time, List<int> *p_indices) const; + _FORCE_INLINE_ void _method_track_get_key_indices_in_range(const MethodTrack *mt, double from_time, double to_time, List<int> *p_indices) const; - float length = 1.0; - float step = 0.1; + double length = 1.0; + real_t step = 0.1; bool loop = false; // bind helpers private: - Array _transform_track_interpolate(int p_track, float p_time) const { + Array _transform_track_interpolate(int p_track, double p_time) const { Vector3 loc; Quaternion rot; Vector3 scale; @@ -223,29 +226,29 @@ private: return ret; } - Vector<int> _value_track_get_key_indices(int p_track, float p_time, float p_delta) const { + Vector<int> _value_track_get_key_indices(int p_track, double p_time, double p_delta) const { List<int> idxs; value_track_get_key_indices(p_track, p_time, p_delta, &idxs); Vector<int> idxr; - for (List<int>::Element *E = idxs.front(); E; E = E->next()) { - idxr.push_back(E->get()); + for (int &E : idxs) { + idxr.push_back(E); } return idxr; } - Vector<int> _method_track_get_key_indices(int p_track, float p_time, float p_delta) const { + Vector<int> _method_track_get_key_indices(int p_track, double p_time, double p_delta) const { List<int> idxs; method_track_get_key_indices(p_track, p_time, p_delta, &idxs); Vector<int> idxr; - for (List<int>::Element *E = idxs.front(); E; E = E->next()) { - idxr.push_back(E->get()); + for (int &E : idxs) { + idxr.push_back(E); } return idxr; } - bool _transform_track_optimize_key(const TKey<TransformKey> &t0, const TKey<TransformKey> &t1, const TKey<TransformKey> &t2, float p_alowed_linear_err, float p_alowed_angular_err, float p_max_optimizable_angle, const Vector3 &p_norm); - void _transform_track_optimize(int p_idx, float p_allowed_linear_err = 0.05, float p_allowed_angular_err = 0.01, float p_max_optimizable_angle = Math_PI * 0.125); + bool _transform_track_optimize_key(const TKey<TransformKey> &t0, const TKey<TransformKey> &t1, const TKey<TransformKey> &t2, real_t p_alowed_linear_err, real_t p_alowed_angular_err, real_t p_max_optimizable_angle, const Vector3 &p_norm); + void _transform_track_optimize(int p_idx, real_t p_allowed_linear_err = 0.05, real_t p_allowed_angular_err = 0.01, real_t p_max_optimizable_angle = Math_PI * 0.125); protected: bool _set(const StringName &p_name, const Variant &p_value); @@ -279,75 +282,75 @@ public: void track_set_enabled(int p_track, bool p_enabled); bool track_is_enabled(int p_track) const; - void track_insert_key(int p_track, float p_time, const Variant &p_key, float p_transition = 1); - void track_set_key_transition(int p_track, int p_key_idx, float p_transition); + void track_insert_key(int p_track, double p_time, const Variant &p_key, real_t p_transition = 1); + void track_set_key_transition(int p_track, int p_key_idx, real_t p_transition); void track_set_key_value(int p_track, int p_key_idx, const Variant &p_value); - void track_set_key_time(int p_track, int p_key_idx, float p_time); - int track_find_key(int p_track, float p_time, bool p_exact = false) const; + void track_set_key_time(int p_track, int p_key_idx, double p_time); + int track_find_key(int p_track, double p_time, bool p_exact = false) const; void track_remove_key(int p_track, int p_idx); - void track_remove_key_at_time(int p_track, float p_time); + void track_remove_key_at_time(int p_track, double p_time); int track_get_key_count(int p_track) const; Variant track_get_key_value(int p_track, int p_key_idx) const; - float track_get_key_time(int p_track, int p_key_idx) const; - float track_get_key_transition(int p_track, int p_key_idx) const; + double track_get_key_time(int p_track, int p_key_idx) const; + real_t track_get_key_transition(int p_track, int p_key_idx) const; - int transform_track_insert_key(int p_track, float p_time, const Vector3 &p_loc, const Quaternion &p_rot = Quaternion(), const Vector3 &p_scale = Vector3()); + int transform_track_insert_key(int p_track, double p_time, const Vector3 &p_loc, const Quaternion &p_rot = Quaternion(), const Vector3 &p_scale = Vector3()); Error transform_track_get_key(int p_track, int p_key, Vector3 *r_loc, Quaternion *r_rot, Vector3 *r_scale) const; void track_set_interpolation_type(int p_track, InterpolationType p_interp); InterpolationType track_get_interpolation_type(int p_track) const; - int bezier_track_insert_key(int p_track, float p_time, float p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle); - void bezier_track_set_key_value(int p_track, int p_index, float p_value); + int bezier_track_insert_key(int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle); + void bezier_track_set_key_value(int p_track, int p_index, real_t p_value); void bezier_track_set_key_in_handle(int p_track, int p_index, const Vector2 &p_handle); void bezier_track_set_key_out_handle(int p_track, int p_index, const Vector2 &p_handle); - float bezier_track_get_key_value(int p_track, int p_index) const; + real_t bezier_track_get_key_value(int p_track, int p_index) const; Vector2 bezier_track_get_key_in_handle(int p_track, int p_index) const; Vector2 bezier_track_get_key_out_handle(int p_track, int p_index) const; - float bezier_track_interpolate(int p_track, float p_time) const; + real_t bezier_track_interpolate(int p_track, double p_time) const; - int audio_track_insert_key(int p_track, float p_time, const RES &p_stream, float p_start_offset = 0, float p_end_offset = 0); + 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); - void audio_track_set_key_start_offset(int p_track, int p_key, float p_offset); - void audio_track_set_key_end_offset(int p_track, int p_key, float p_offset); + 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; - float audio_track_get_key_start_offset(int p_track, int p_key) const; - float audio_track_get_key_end_offset(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; - int animation_track_insert_key(int p_track, float p_time, const StringName &p_animation); + int animation_track_insert_key(int p_track, double p_time, const StringName &p_animation); void animation_track_set_key_animation(int p_track, int p_key, const StringName &p_animation); StringName animation_track_get_key_animation(int p_track, int p_key) const; void track_set_interpolation_loop_wrap(int p_track, bool p_enable); bool track_get_interpolation_loop_wrap(int p_track) const; - Error transform_track_interpolate(int p_track, float p_time, Vector3 *r_loc, Quaternion *r_rot, Vector3 *r_scale) const; + Error transform_track_interpolate(int p_track, double p_time, Vector3 *r_loc, Quaternion *r_rot, Vector3 *r_scale) const; - Variant value_track_interpolate(int p_track, float p_time) const; - void value_track_get_key_indices(int p_track, float p_time, float p_delta, List<int> *p_indices) const; + Variant value_track_interpolate(int p_track, double p_time) const; + void value_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices) const; void value_track_set_update_mode(int p_track, UpdateMode p_mode); UpdateMode value_track_get_update_mode(int p_track) const; - void method_track_get_key_indices(int p_track, float p_time, float p_delta, List<int> *p_indices) const; + void method_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices) const; Vector<Variant> method_track_get_params(int p_track, int p_key_idx) const; StringName method_track_get_name(int p_track, int p_key_idx) const; void copy_track(int p_track, Ref<Animation> p_to_animation); - void track_get_key_indices_in_range(int p_track, float p_time, float p_delta, List<int> *p_indices) const; + void track_get_key_indices_in_range(int p_track, double p_time, double p_delta, List<int> *p_indices) const; - void set_length(float p_length); - float get_length() const; + void set_length(real_t p_length); + real_t get_length() const; void set_loop(bool p_enabled); bool has_loop() const; - void set_step(float p_step); - float get_step() const; + void set_step(real_t p_step); + real_t get_step() const; void clear(); - void optimize(float p_allowed_linear_err = 0.05, float p_allowed_angular_err = 0.01, float p_max_optimizable_angle = Math_PI * 0.125); + void optimize(real_t p_allowed_linear_err = 0.05, real_t p_allowed_angular_err = 0.01, real_t p_max_optimizable_angle = Math_PI * 0.125); Animation(); ~Animation(); diff --git a/scene/resources/audio_stream_sample.cpp b/scene/resources/audio_stream_sample.cpp index 8ffd2df112..d018103e64 100644 --- a/scene/resources/audio_stream_sample.cpp +++ b/scene/resources/audio_stream_sample.cpp @@ -221,12 +221,12 @@ void AudioStreamPlaybackSample::do_resample(const Depth *p_src, AudioFrame *p_ds } } -void AudioStreamPlaybackSample::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) { +int AudioStreamPlaybackSample::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) { if (!base->data || !active) { for (int i = 0; i < p_frames; i++) { p_buffer[i] = AudioFrame(0, 0); } - return; + return 0; } int len = base->data_bytes; @@ -261,11 +261,11 @@ void AudioStreamPlaybackSample::mix(AudioFrame *p_buffer, float p_rate_scale, in sign = -1; } - float global_rate_scale = AudioServer::get_singleton()->get_global_rate_scale(); - float base_rate = AudioServer::get_singleton()->get_mix_rate() * global_rate_scale; + float base_rate = AudioServer::get_singleton()->get_mix_rate(); float srate = base->mix_rate; srate *= p_rate_scale; - float fincrement = srate / base_rate; + float playback_speed_scale = AudioServer::get_singleton()->get_playback_speed_scale(); + float fincrement = (srate * playback_speed_scale) / base_rate; int32_t increment = int32_t(MAX(fincrement * MIX_FRAC_LEN, 1)); increment *= sign; @@ -395,12 +395,15 @@ void AudioStreamPlaybackSample::mix(AudioFrame *p_buffer, float p_rate_scale, in } if (todo) { + int mixed_frames = p_frames - todo; //bit was missing from mix int todo_ofs = p_frames - todo; for (int i = todo_ofs; i < p_frames; i++) { p_buffer[i] = AudioFrame(0, 0); } + return mixed_frames; } + return p_frames; } AudioStreamPlaybackSample::AudioStreamPlaybackSample() {} @@ -477,6 +480,10 @@ float AudioStreamSample::get_length() const { return float(len) / mix_rate; } +bool AudioStreamSample::is_monophonic() const { + return false; +} + void AudioStreamSample::set_data(const Vector<uint8_t> &p_data) { AudioServer::get_singleton()->lock(); if (data) { diff --git a/scene/resources/audio_stream_sample.h b/scene/resources/audio_stream_sample.h index 70b8ba79ad..24198e3c98 100644 --- a/scene/resources/audio_stream_sample.h +++ b/scene/resources/audio_stream_sample.h @@ -73,7 +73,7 @@ public: virtual float get_playback_position() const override; virtual void seek(float p_time) override; - virtual void mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override; + virtual int mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override; AudioStreamPlaybackSample(); }; @@ -136,6 +136,8 @@ public: virtual float get_length() const override; //if supported, otherwise return 0 + virtual bool is_monophonic() const override; + void set_data(const Vector<uint8_t> &p_data); Vector<uint8_t> get_data() const; diff --git a/scene/resources/canvas_item_material.cpp b/scene/resources/canvas_item_material.cpp new file mode 100644 index 0000000000..7501efea9e --- /dev/null +++ b/scene/resources/canvas_item_material.cpp @@ -0,0 +1,306 @@ +/*************************************************************************/ +/* canvas_item_material.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "canvas_item_material.h" + +#include "core/version.h" + +Mutex CanvasItemMaterial::material_mutex; +SelfList<CanvasItemMaterial>::List *CanvasItemMaterial::dirty_materials = nullptr; +Map<CanvasItemMaterial::MaterialKey, CanvasItemMaterial::ShaderData> CanvasItemMaterial::shader_map; +CanvasItemMaterial::ShaderNames *CanvasItemMaterial::shader_names = nullptr; + +void CanvasItemMaterial::init_shaders() { + dirty_materials = memnew(SelfList<CanvasItemMaterial>::List); + + shader_names = memnew(ShaderNames); + + shader_names->particles_anim_h_frames = "particles_anim_h_frames"; + shader_names->particles_anim_v_frames = "particles_anim_v_frames"; + shader_names->particles_anim_loop = "particles_anim_loop"; +} + +void CanvasItemMaterial::finish_shaders() { + memdelete(dirty_materials); + memdelete(shader_names); + dirty_materials = nullptr; +} + +void CanvasItemMaterial::_update_shader() { + dirty_materials->remove(&element); + + MaterialKey mk = _compute_key(); + if (mk.key == current_key.key) { + return; //no update required in the end + } + + if (shader_map.has(current_key)) { + shader_map[current_key].users--; + if (shader_map[current_key].users == 0) { + //deallocate shader, as it's no longer in use + RS::get_singleton()->free(shader_map[current_key].shader); + shader_map.erase(current_key); + } + } + + current_key = mk; + + if (shader_map.has(mk)) { + RS::get_singleton()->material_set_shader(_get_material(), shader_map[mk].shader); + shader_map[mk].users++; + return; + } + + //must create a shader! + + // Add a comment to describe the shader origin (useful when converting to ShaderMaterial). + String code = "// NOTE: Shader automatically converted from " VERSION_NAME " " VERSION_FULL_CONFIG "'s CanvasItemMaterial.\n\n"; + + code += "shader_type canvas_item;\nrender_mode "; + switch (blend_mode) { + case BLEND_MODE_MIX: + code += "blend_mix"; + break; + case BLEND_MODE_ADD: + code += "blend_add"; + break; + case BLEND_MODE_SUB: + code += "blend_sub"; + break; + case BLEND_MODE_MUL: + code += "blend_mul"; + break; + case BLEND_MODE_PREMULT_ALPHA: + code += "blend_premul_alpha"; + break; + case BLEND_MODE_DISABLED: + code += "blend_disabled"; + break; + } + + switch (light_mode) { + case LIGHT_MODE_NORMAL: + break; + case LIGHT_MODE_UNSHADED: + code += ",unshaded"; + break; + case LIGHT_MODE_LIGHT_ONLY: + code += ",light_only"; + break; + } + + code += ";\n"; + + if (particles_animation) { + code += "uniform int particles_anim_h_frames;\n"; + code += "uniform int particles_anim_v_frames;\n"; + code += "uniform bool particles_anim_loop;\n\n"; + + code += "void vertex() {\n"; + code += " float h_frames = float(particles_anim_h_frames);\n"; + code += " float v_frames = float(particles_anim_v_frames);\n"; + code += " VERTEX.xy /= vec2(h_frames, v_frames);\n"; + code += " float particle_total_frames = float(particles_anim_h_frames * particles_anim_v_frames);\n"; + code += " float particle_frame = floor(INSTANCE_CUSTOM.z * float(particle_total_frames));\n"; + code += " if (!particles_anim_loop) {\n"; + code += " particle_frame = clamp(particle_frame, 0.0, particle_total_frames - 1.0);\n"; + code += " } else {\n"; + code += " particle_frame = mod(particle_frame, particle_total_frames);\n"; + code += " }"; + code += " UV /= vec2(h_frames, v_frames);\n"; + code += " UV += vec2(mod(particle_frame, h_frames) / h_frames, floor(particle_frame / h_frames) / v_frames);\n"; + code += "}\n"; + } + + ShaderData shader_data; + shader_data.shader = RS::get_singleton()->shader_create(); + shader_data.users = 1; + + RS::get_singleton()->shader_set_code(shader_data.shader, code); + + shader_map[mk] = shader_data; + + RS::get_singleton()->material_set_shader(_get_material(), shader_data.shader); +} + +void CanvasItemMaterial::flush_changes() { + MutexLock lock(material_mutex); + + while (dirty_materials->first()) { + dirty_materials->first()->self()->_update_shader(); + } +} + +void CanvasItemMaterial::_queue_shader_change() { + MutexLock lock(material_mutex); + + if (!element.in_list()) { + dirty_materials->add(&element); + } +} + +bool CanvasItemMaterial::_is_shader_dirty() const { + MutexLock lock(material_mutex); + + return element.in_list(); +} + +void CanvasItemMaterial::set_blend_mode(BlendMode p_blend_mode) { + blend_mode = p_blend_mode; + _queue_shader_change(); +} + +CanvasItemMaterial::BlendMode CanvasItemMaterial::get_blend_mode() const { + return blend_mode; +} + +void CanvasItemMaterial::set_light_mode(LightMode p_light_mode) { + light_mode = p_light_mode; + _queue_shader_change(); +} + +CanvasItemMaterial::LightMode CanvasItemMaterial::get_light_mode() const { + return light_mode; +} + +void CanvasItemMaterial::set_particles_animation(bool p_particles_anim) { + particles_animation = p_particles_anim; + _queue_shader_change(); + notify_property_list_changed(); +} + +bool CanvasItemMaterial::get_particles_animation() const { + return particles_animation; +} + +void CanvasItemMaterial::set_particles_anim_h_frames(int p_frames) { + particles_anim_h_frames = p_frames; + RS::get_singleton()->material_set_param(_get_material(), shader_names->particles_anim_h_frames, p_frames); +} + +int CanvasItemMaterial::get_particles_anim_h_frames() const { + return particles_anim_h_frames; +} + +void CanvasItemMaterial::set_particles_anim_v_frames(int p_frames) { + particles_anim_v_frames = p_frames; + RS::get_singleton()->material_set_param(_get_material(), shader_names->particles_anim_v_frames, p_frames); +} + +int CanvasItemMaterial::get_particles_anim_v_frames() const { + return particles_anim_v_frames; +} + +void CanvasItemMaterial::set_particles_anim_loop(bool p_loop) { + particles_anim_loop = p_loop; + RS::get_singleton()->material_set_param(_get_material(), shader_names->particles_anim_loop, particles_anim_loop); +} + +bool CanvasItemMaterial::get_particles_anim_loop() const { + return particles_anim_loop; +} + +void CanvasItemMaterial::_validate_property(PropertyInfo &property) const { + if (property.name.begins_with("particles_anim_") && !particles_animation) { + property.usage = PROPERTY_USAGE_NONE; + } +} + +RID CanvasItemMaterial::get_shader_rid() const { + ERR_FAIL_COND_V(!shader_map.has(current_key), RID()); + return shader_map[current_key].shader; +} + +Shader::Mode CanvasItemMaterial::get_shader_mode() const { + return Shader::MODE_CANVAS_ITEM; +} + +void CanvasItemMaterial::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_blend_mode", "blend_mode"), &CanvasItemMaterial::set_blend_mode); + ClassDB::bind_method(D_METHOD("get_blend_mode"), &CanvasItemMaterial::get_blend_mode); + + ClassDB::bind_method(D_METHOD("set_light_mode", "light_mode"), &CanvasItemMaterial::set_light_mode); + ClassDB::bind_method(D_METHOD("get_light_mode"), &CanvasItemMaterial::get_light_mode); + + ClassDB::bind_method(D_METHOD("set_particles_animation", "particles_anim"), &CanvasItemMaterial::set_particles_animation); + ClassDB::bind_method(D_METHOD("get_particles_animation"), &CanvasItemMaterial::get_particles_animation); + + ClassDB::bind_method(D_METHOD("set_particles_anim_h_frames", "frames"), &CanvasItemMaterial::set_particles_anim_h_frames); + ClassDB::bind_method(D_METHOD("get_particles_anim_h_frames"), &CanvasItemMaterial::get_particles_anim_h_frames); + + ClassDB::bind_method(D_METHOD("set_particles_anim_v_frames", "frames"), &CanvasItemMaterial::set_particles_anim_v_frames); + ClassDB::bind_method(D_METHOD("get_particles_anim_v_frames"), &CanvasItemMaterial::get_particles_anim_v_frames); + + ClassDB::bind_method(D_METHOD("set_particles_anim_loop", "loop"), &CanvasItemMaterial::set_particles_anim_loop); + ClassDB::bind_method(D_METHOD("get_particles_anim_loop"), &CanvasItemMaterial::get_particles_anim_loop); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_mode", PROPERTY_HINT_ENUM, "Mix,Add,Subtract,Multiply,Premultiplied Alpha"), "set_blend_mode", "get_blend_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "light_mode", PROPERTY_HINT_ENUM, "Normal,Unshaded,Light Only"), "set_light_mode", "get_light_mode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "particles_animation"), "set_particles_animation", "get_particles_animation"); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "particles_anim_h_frames", PROPERTY_HINT_RANGE, "1,128,1"), "set_particles_anim_h_frames", "get_particles_anim_h_frames"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "particles_anim_v_frames", PROPERTY_HINT_RANGE, "1,128,1"), "set_particles_anim_v_frames", "get_particles_anim_v_frames"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "particles_anim_loop"), "set_particles_anim_loop", "get_particles_anim_loop"); + + BIND_ENUM_CONSTANT(BLEND_MODE_MIX); + BIND_ENUM_CONSTANT(BLEND_MODE_ADD); + BIND_ENUM_CONSTANT(BLEND_MODE_SUB); + BIND_ENUM_CONSTANT(BLEND_MODE_MUL); + BIND_ENUM_CONSTANT(BLEND_MODE_PREMULT_ALPHA); + + BIND_ENUM_CONSTANT(LIGHT_MODE_NORMAL); + BIND_ENUM_CONSTANT(LIGHT_MODE_UNSHADED); + BIND_ENUM_CONSTANT(LIGHT_MODE_LIGHT_ONLY); +} + +CanvasItemMaterial::CanvasItemMaterial() : + element(this) { + set_particles_anim_h_frames(1); + set_particles_anim_v_frames(1); + set_particles_anim_loop(false); + + current_key.invalid_key = 1; + _queue_shader_change(); +} + +CanvasItemMaterial::~CanvasItemMaterial() { + MutexLock lock(material_mutex); + + if (shader_map.has(current_key)) { + shader_map[current_key].users--; + if (shader_map[current_key].users == 0) { + //deallocate shader, as it's no longer in use + RS::get_singleton()->free(shader_map[current_key].shader); + shader_map.erase(current_key); + } + + RS::get_singleton()->material_set_shader(_get_material(), RID()); + } +} diff --git a/scene/resources/canvas_item_material.h b/scene/resources/canvas_item_material.h new file mode 100644 index 0000000000..0a813e0ae5 --- /dev/null +++ b/scene/resources/canvas_item_material.h @@ -0,0 +1,151 @@ +/*************************************************************************/ +/* canvas_item_material.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef CANVAS_ITEM_MATERIAL_H +#define CANVAS_ITEM_MATERIAL_H + +#include "scene/resources/material.h" + +class CanvasItemMaterial : public Material { + GDCLASS(CanvasItemMaterial, Material); + +public: + enum BlendMode { + BLEND_MODE_MIX, + BLEND_MODE_ADD, + BLEND_MODE_SUB, + BLEND_MODE_MUL, + BLEND_MODE_PREMULT_ALPHA, + BLEND_MODE_DISABLED + }; + + enum LightMode { + LIGHT_MODE_NORMAL, + LIGHT_MODE_UNSHADED, + LIGHT_MODE_LIGHT_ONLY + }; + +private: + union MaterialKey { + struct { + uint32_t blend_mode : 4; + uint32_t light_mode : 4; + uint32_t particles_animation : 1; + uint32_t invalid_key : 1; + }; + + uint32_t key = 0; + + bool operator<(const MaterialKey &p_key) const { + return key < p_key.key; + } + }; + + struct ShaderNames { + StringName particles_anim_h_frames; + StringName particles_anim_v_frames; + StringName particles_anim_loop; + }; + + static ShaderNames *shader_names; + + struct ShaderData { + RID shader; + int users = 0; + }; + + static Map<MaterialKey, ShaderData> shader_map; + + MaterialKey current_key; + + _FORCE_INLINE_ MaterialKey _compute_key() const { + MaterialKey mk; + mk.key = 0; + mk.blend_mode = blend_mode; + mk.light_mode = light_mode; + mk.particles_animation = particles_animation; + return mk; + } + + static Mutex material_mutex; + static SelfList<CanvasItemMaterial>::List *dirty_materials; + SelfList<CanvasItemMaterial> element; + + void _update_shader(); + _FORCE_INLINE_ void _queue_shader_change(); + _FORCE_INLINE_ bool _is_shader_dirty() const; + + BlendMode blend_mode = BLEND_MODE_MIX; + 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; + +protected: + static void _bind_methods(); + void _validate_property(PropertyInfo &property) const override; + +public: + void set_blend_mode(BlendMode p_blend_mode); + BlendMode get_blend_mode() const; + + void set_light_mode(LightMode p_light_mode); + LightMode get_light_mode() const; + + void set_particles_animation(bool p_particles_anim); + bool get_particles_animation() const; + + void set_particles_anim_h_frames(int p_frames); + int get_particles_anim_h_frames() const; + void set_particles_anim_v_frames(int p_frames); + int get_particles_anim_v_frames() const; + + void set_particles_anim_loop(bool p_loop); + bool get_particles_anim_loop() const; + + static void init_shaders(); + static void finish_shaders(); + static void flush_changes(); + + virtual RID get_shader_rid() const override; + + virtual Shader::Mode get_shader_mode() const override; + + CanvasItemMaterial(); + virtual ~CanvasItemMaterial(); +}; + +VARIANT_ENUM_CAST(CanvasItemMaterial::BlendMode) +VARIANT_ENUM_CAST(CanvasItemMaterial::LightMode) + +#endif // CANVAS_ITEM_MATERIAL_H diff --git a/scene/resources/capsule_shape_2d.cpp b/scene/resources/capsule_shape_2d.cpp index e5edba8a67..0818e4fd99 100644 --- a/scene/resources/capsule_shape_2d.cpp +++ b/scene/resources/capsule_shape_2d.cpp @@ -38,11 +38,11 @@ Vector<Vector2> CapsuleShape2D::_get_points() const { Vector<Vector2> points; const real_t turn_step = Math_TAU / 24.0; for (int i = 0; i < 24; i++) { - Vector2 ofs = Vector2(0, (i > 6 && i <= 18) ? -get_height() * 0.5 : get_height() * 0.5); + Vector2 ofs = Vector2(0, (i > 6 && i <= 18) ? -height * 0.5 + radius : height * 0.5 - radius); - points.push_back(Vector2(Math::sin(i * turn_step), Math::cos(i * turn_step)) * get_radius() + ofs); + points.push_back(Vector2(Math::sin(i * turn_step), Math::cos(i * turn_step)) * radius + ofs); if (i == 6 || i == 18) { - points.push_back(Vector2(Math::sin(i * turn_step), Math::cos(i * turn_step)) * get_radius() - ofs); + points.push_back(Vector2(Math::sin(i * turn_step), Math::cos(i * turn_step)) * radius - ofs); } } @@ -60,6 +60,9 @@ void CapsuleShape2D::_update_shape() { void CapsuleShape2D::set_radius(real_t p_radius) { radius = p_radius; + if (radius > height * 0.5) { + height = radius * 2.0; + } _update_shape(); } @@ -69,10 +72,9 @@ real_t CapsuleShape2D::get_radius() const { void CapsuleShape2D::set_height(real_t p_height) { height = p_height; - if (height < 0) { - height = 0; + if (radius > height * 0.5) { + radius = height * 0.5; } - _update_shape(); } @@ -93,15 +95,11 @@ void CapsuleShape2D::draw(const RID &p_to_rid, const Color &p_color) { } Rect2 CapsuleShape2D::get_rect() const { - Vector2 he = Point2(get_radius(), get_radius() + get_height() * 0.5); - Rect2 rect; - rect.position = -he; - rect.size = he * 2.0; - return rect; + return Rect2(0, 0, radius, height); } real_t CapsuleShape2D::get_enclosing_radius() const { - return radius + height * 0.5; + return height * 0.5; } void CapsuleShape2D::_bind_methods() { @@ -113,6 +111,8 @@ void CapsuleShape2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius"), "set_radius", "get_radius"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height"), "set_height", "get_height"); + ADD_LINKED_PROPERTY("radius", "height"); + ADD_LINKED_PROPERTY("height", "radius"); } CapsuleShape2D::CapsuleShape2D() : diff --git a/scene/resources/capsule_shape_2d.h b/scene/resources/capsule_shape_2d.h index 439b67e8c3..37b6c52c0e 100644 --- a/scene/resources/capsule_shape_2d.h +++ b/scene/resources/capsule_shape_2d.h @@ -36,7 +36,7 @@ class CapsuleShape2D : public Shape2D { GDCLASS(CapsuleShape2D, Shape2D); - real_t height = 20.0; + real_t height = 30.0; real_t radius = 10.0; void _update_shape(); diff --git a/scene/resources/capsule_shape_3d.cpp b/scene/resources/capsule_shape_3d.cpp index 226fe1ecd2..afec7b1877 100644 --- a/scene/resources/capsule_shape_3d.cpp +++ b/scene/resources/capsule_shape_3d.cpp @@ -37,7 +37,7 @@ Vector<Vector3> CapsuleShape3D::get_debug_mesh_lines() const { Vector<Vector3> points; - Vector3 d(0, height * 0.5, 0); + Vector3 d(0, height * 0.5 - radius, 0); for (int i = 0; i < 360; i++) { float ra = Math::deg2rad((float)i); float rb = Math::deg2rad((float)i + 1); @@ -67,7 +67,7 @@ Vector<Vector3> CapsuleShape3D::get_debug_mesh_lines() const { } real_t CapsuleShape3D::get_enclosing_radius() const { - return radius + height * 0.5; + return height * 0.5; } void CapsuleShape3D::_update_shape() { @@ -80,6 +80,9 @@ void CapsuleShape3D::_update_shape() { void CapsuleShape3D::set_radius(float p_radius) { radius = p_radius; + if (radius > height * 0.5) { + height = radius * 2.0; + } _update_shape(); notify_change_to_owners(); } @@ -90,6 +93,9 @@ float CapsuleShape3D::get_radius() const { void CapsuleShape3D::set_height(float p_height) { height = p_height; + if (radius > height * 0.5) { + radius = height * 0.5; + } _update_shape(); notify_change_to_owners(); } @@ -106,6 +112,8 @@ void CapsuleShape3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,4096,0.001"), "set_radius", "get_radius"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,4096,0.001"), "set_height", "get_height"); + ADD_LINKED_PROPERTY("radius", "height"); + ADD_LINKED_PROPERTY("height", "radius"); } CapsuleShape3D::CapsuleShape3D() : diff --git a/scene/resources/capsule_shape_3d.h b/scene/resources/capsule_shape_3d.h index 25645ecf9d..f09b4fb77d 100644 --- a/scene/resources/capsule_shape_3d.h +++ b/scene/resources/capsule_shape_3d.h @@ -36,7 +36,7 @@ class CapsuleShape3D : public Shape3D { GDCLASS(CapsuleShape3D, Shape3D); float radius = 1.0; - float height = 1.0; + float height = 3.0; protected: static void _bind_methods(); diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp index 846da39221..a364a27e80 100644 --- a/scene/resources/curve.cpp +++ b/scene/resources/curve.cpp @@ -286,7 +286,7 @@ void Curve::set_min_value(float p_min) { } // Note: min and max are indicative values, // it's still possible that existing points are out of range at this point. - emit_signal(SIGNAL_RANGE_CHANGED); + emit_signal(SNAME(SIGNAL_RANGE_CHANGED)); } void Curve::set_max_value(float p_max) { @@ -296,7 +296,7 @@ void Curve::set_max_value(float p_max) { _minmax_set_once |= 0b01; // second bit is "max set" _max_value = p_max; } - emit_signal(SIGNAL_RANGE_CHANGED); + emit_signal(SNAME(SIGNAL_RANGE_CHANGED)); } real_t Curve::interpolate(real_t offset) const { @@ -662,19 +662,27 @@ void Curve2D::_bake() const { if (points.size() == 0) { baked_point_cache.resize(0); + baked_dist_cache.resize(0); return; } if (points.size() == 1) { baked_point_cache.resize(1); baked_point_cache.set(0, points[0].pos); + + baked_dist_cache.resize(1); + baked_dist_cache.set(0, 0.0); return; } Vector2 pos = points[0].pos; + float dist = 0.0; + List<Vector2> pointlist; + List<float> distlist; pointlist.push_back(pos); //start always from origin + distlist.push_back(0.0); for (int i = 0; i < points.size() - 1; i++) { float step = 0.1; // at least 10 substeps ought to be enough? @@ -712,7 +720,10 @@ void Curve2D::_bake() const { pos = npp; p = mid; + dist += d; + pointlist.push_back(pos); + distlist.push_back(dist); } else { p = np; } @@ -722,16 +733,20 @@ void Curve2D::_bake() const { Vector2 lastpos = points[points.size() - 1].pos; float rem = pos.distance_to(lastpos); - baked_max_ofs = (pointlist.size() - 1) * bake_interval + rem; + 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()); + Vector2 *w = baked_point_cache.ptrw(); - int idx = 0; + float *wd = baked_dist_cache.ptrw(); - for (List<Vector2>::Element *E = pointlist.front(); E; E = E->next()) { - w[idx] = E->get(); - idx++; + for (int i = 0; i < pointlist.size(); i++) { + w[i] = pointlist[i]; + wd[i] = distlist[i]; } } @@ -766,19 +781,26 @@ Vector2 Curve2D::interpolate_baked(float p_offset, bool p_cubic) const { return r[bpc - 1]; } - int idx = Math::floor((double)p_offset / (double)bake_interval); - float frac = Math::fmod(p_offset, (float)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, end = bpc, idx = (end + start) / 2; + // binary search to find baked points + while (start < idx) { + float offset = baked_dist_cache[idx]; + if (p_offset <= offset) { + end = idx; + } else { + start = idx; } - } else { - frac /= bake_interval; + idx = (end + start) / 2; } + float offset_begin = baked_dist_cache[idx]; + float offset_end = baked_dist_cache[idx + 1]; + + float idx_interval = offset_end - offset_begin; + ERR_FAIL_COND_V_MSG(p_offset < offset_begin || p_offset > offset_end, Vector2(), "failed to find baked segment"); + + float frac = (p_offset - offset_begin) / idx_interval; + if (p_cubic) { Vector2 pre = idx > 0 ? r[idx - 1] : r[idx]; Vector2 post = (idx < (bpc - 2)) ? r[idx + 2] : r[idx + 1]; @@ -1145,6 +1167,7 @@ void Curve3D::_bake() const { baked_point_cache.resize(0); baked_tilt_cache.resize(0); baked_up_vector_cache.resize(0); + baked_dist_cache.resize(0); return; } @@ -1153,6 +1176,8 @@ void Curve3D::_bake() const { baked_point_cache.set(0, points[0].pos); baked_tilt_cache.resize(1); baked_tilt_cache.set(0, points[0].tilt); + baked_dist_cache.resize(1); + baked_dist_cache.set(0, 0.0); if (up_vector_enabled) { baked_up_vector_cache.resize(1); @@ -1165,8 +1190,12 @@ void Curve3D::_bake() const { } Vector3 pos = points[0].pos; + float dist = 0.0; List<Plane> pointlist; + List<float> distlist; + pointlist.push_back(Plane(pos, points[0].tilt)); + distlist.push_back(0.0); for (int i = 0; i < points.size() - 1; i++) { float step = 0.1; // at least 10 substeps ought to be enough? @@ -1207,7 +1236,10 @@ void Curve3D::_bake() const { Plane post; post.normal = pos; post.d = Math::lerp(points[i].tilt, points[i + 1].tilt, mid); + dist += d; + pointlist.push_back(post); + distlist.push_back(dist); } else { p = np; } @@ -1218,8 +1250,10 @@ void Curve3D::_bake() const { float lastilt = points[points.size() - 1].tilt; float rem = pos.distance_to(lastpos); - baked_max_ofs = (pointlist.size() - 1) * bake_interval + rem; + 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(); @@ -1231,6 +1265,9 @@ void Curve3D::_bake() const { baked_up_vector_cache.resize(up_vector_enabled ? pointlist.size() : 0); Vector3 *up_write = baked_up_vector_cache.ptrw(); + baked_dist_cache.resize(pointlist.size()); + float *wd = baked_dist_cache.ptrw(); + Vector3 sideways; Vector3 up; Vector3 forward; @@ -1239,9 +1276,10 @@ void Curve3D::_bake() const { Vector3 prev_up = Vector3(0, 1, 0); Vector3 prev_forward = Vector3(0, 0, 1); - for (List<Plane>::Element *E = pointlist.front(); E; E = E->next()) { - w[idx] = E->get().normal; - wt[idx] = E->get().d; + for (const Plane &E : pointlist) { + w[idx] = E.normal; + wt[idx] = E.d; + wd[idx] = distlist[idx]; if (!up_vector_enabled) { idx++; @@ -1308,19 +1346,26 @@ Vector3 Curve3D::interpolate_baked(float p_offset, bool p_cubic) const { return r[bpc - 1]; } - int idx = Math::floor((double)p_offset / (double)bake_interval); - float 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, end = bpc, idx = (end + start) / 2; + // binary search to find baked points + while (start < idx) { + float offset = baked_dist_cache[idx]; + if (p_offset <= offset) { + end = idx; + } else { + start = idx; } - } else { - frac /= bake_interval; + idx = (end + start) / 2; } + float offset_begin = baked_dist_cache[idx]; + float offset_end = baked_dist_cache[idx + 1]; + + float idx_interval = offset_end - offset_begin; + ERR_FAIL_COND_V_MSG(p_offset < offset_begin || p_offset > offset_end, Vector3(), "failed to find baked segment"); + + float frac = (p_offset - offset_begin) / idx_interval; + if (p_cubic) { Vector3 pre = idx > 0 ? r[idx - 1] : r[idx]; Vector3 post = (idx < (bpc - 2)) ? r[idx + 2] : r[idx + 1]; @@ -1366,7 +1411,7 @@ float Curve3D::interpolate_baked_tilt(float p_offset) const { frac /= bake_interval; } - return Math::lerp(r[idx], r[idx + 1], frac); + return Math::lerp(r[idx], r[idx + 1], (real_t)frac); } Vector3 Curve3D::interpolate_baked_up_vector(float p_offset, bool p_apply_tilt) const { @@ -1424,7 +1469,7 @@ PackedVector3Array Curve3D::get_baked_points() const { return baked_point_cache; } -PackedFloat32Array Curve3D::get_baked_tilts() const { +Vector<real_t> Curve3D::get_baked_tilts() const { if (baked_cache_dirty) { _bake(); } @@ -1545,7 +1590,7 @@ Dictionary Curve3D::_get_data() const { PackedVector3Array d; d.resize(points.size() * 3); Vector3 *w = d.ptrw(); - PackedFloat32Array t; + Vector<real_t> t; t.resize(points.size()); real_t *wt = t.ptrw(); @@ -1571,7 +1616,7 @@ void Curve3D::_set_data(const Dictionary &p_data) { ERR_FAIL_COND(pc % 3 != 0); points.resize(pc / 3); const Vector3 *r = rp.ptr(); - PackedFloat32Array rtl = p_data["tilts"]; + Vector<real_t> rtl = p_data["tilts"]; const real_t *rt = rtl.ptr(); for (int i = 0; i < points.size(); i++) { diff --git a/scene/resources/curve.h b/scene/resources/curve.h index 746c6fa597..5808fd6508 100644 --- a/scene/resources/curve.h +++ b/scene/resources/curve.h @@ -161,6 +161,7 @@ class Curve2D : public Resource { mutable bool baked_cache_dirty = false; mutable PackedVector2Array baked_point_cache; + mutable PackedFloat32Array baked_dist_cache; mutable float baked_max_ofs = 0.0; void _bake() const; @@ -222,8 +223,9 @@ class Curve3D : public Resource { mutable bool baked_cache_dirty = false; mutable PackedVector3Array baked_point_cache; - mutable PackedFloat32Array baked_tilt_cache; + mutable Vector<real_t> baked_tilt_cache; mutable PackedVector3Array baked_up_vector_cache; + mutable PackedFloat32Array baked_dist_cache; mutable float baked_max_ofs = 0.0; void _bake() const; @@ -265,7 +267,7 @@ public: float interpolate_baked_tilt(float p_offset) const; Vector3 interpolate_baked_up_vector(float p_offset, bool p_apply_tilt = false) const; PackedVector3Array get_baked_points() const; //useful for going through - PackedFloat32Array get_baked_tilts() const; //useful for going through + Vector<real_t> get_baked_tilts() const; //useful for going through PackedVector3Array get_baked_up_vectors() const; Vector3 get_closest_point(const Vector3 &p_to_point) const; float get_closest_offset(const Vector3 &p_to_point) const; diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 5464a46df4..fa3824e6eb 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -212,26 +212,6 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("outline_size", "LinkButton", 0); theme->set_constant("underline_spacing", "LinkButton", 2 * scale); - // ColorPickerButton - - theme->set_stylebox("normal", "ColorPickerButton", sb_button_normal); - theme->set_stylebox("pressed", "ColorPickerButton", sb_button_pressed); - theme->set_stylebox("hover", "ColorPickerButton", sb_button_hover); - theme->set_stylebox("disabled", "ColorPickerButton", sb_button_disabled); - theme->set_stylebox("focus", "ColorPickerButton", sb_button_focus); - - theme->set_font("font", "ColorPickerButton", Ref<Font>()); - theme->set_font_size("font_size", "ColorPickerButton", -1); - - theme->set_color("font_color", "ColorPickerButton", Color(1, 1, 1, 1)); - theme->set_color("font_pressed_color", "ColorPickerButton", Color(0.8, 0.8, 0.8, 1)); - theme->set_color("font_hover_color", "ColorPickerButton", Color(1, 1, 1, 1)); - 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("outline_size", "ColorPickerButton", 0); - // OptionButton Ref<StyleBox> sb_optbutton_focus = sb_expand(make_stylebox(button_focus_png, 4, 4, 4, 4, 6, 2, 6, 2), 2, 2, 2, 2); @@ -390,6 +370,15 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("shadow_outline_size", "Label", 1 * scale); theme->set_constant("line_spacing", "Label", 3 * scale); + theme->set_type_variation("HeaderSmall", "Label"); + theme->set_font_size("font_size", "HeaderSmall", default_font_size + 4); + + theme->set_type_variation("HeaderMedium", "Label"); + theme->set_font_size("font_size", "HeaderMedium", default_font_size + 8); + + theme->set_type_variation("HeaderLarge", "Label"); + theme->set_font_size("font_size", "HeaderLarge", default_font_size + 12); + // LineEdit theme->set_stylebox("normal", "LineEdit", make_stylebox(line_edit_png, 5, 5, 5, 5)); @@ -448,7 +437,6 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("current_line_color", "TextEdit", Color(0.25, 0.25, 0.26, 0.8)); theme->set_color("caret_color", "TextEdit", control_font_color); theme->set_color("caret_background_color", "TextEdit", Color(0, 0, 0)); - theme->set_color("brace_mismatch_color", "TextEdit", Color(1, 0.2, 0.2)); theme->set_color("word_highlighted_color", "TextEdit", Color(0.8, 0.9, 0.9, 0.15)); theme->set_constant("line_spacing", "TextEdit", 4 * scale); @@ -493,8 +481,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("caret_background_color", "CodeEdit", Color(0, 0, 0)); theme->set_color("brace_mismatch_color", "CodeEdit", Color(1, 0.2, 0.2)); theme->set_color("line_number_color", "CodeEdit", Color(0.67, 0.67, 0.67, 0.4)); - theme->set_color("safe_line_number_color", "CodeEdit", Color(0.67, 0.78, 0.67, 0.6)); theme->set_color("word_highlighted_color", "CodeEdit", Color(0.8, 0.9, 0.9, 0.15)); + theme->set_color("line_length_guideline_color", "CodeEdit", Color(0.3, 0.5, 0.8, 0.1)); theme->set_constant("completion_lines", "CodeEdit", 7); theme->set_constant("completion_max_width", "CodeEdit", 50); @@ -514,8 +502,10 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("increment", "HScrollBar", empty_icon); theme->set_icon("increment_highlight", "HScrollBar", empty_icon); + theme->set_icon("increment_pressed", "HScrollBar", empty_icon); theme->set_icon("decrement", "HScrollBar", empty_icon); theme->set_icon("decrement_highlight", "HScrollBar", empty_icon); + theme->set_icon("decrement_pressed", "HScrollBar", empty_icon); // VScrollBar @@ -527,8 +517,10 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("increment", "VScrollBar", empty_icon); theme->set_icon("increment_highlight", "VScrollBar", empty_icon); + theme->set_icon("increment_pressed", "VScrollBar", empty_icon); theme->set_icon("decrement", "VScrollBar", empty_icon); theme->set_icon("decrement_highlight", "VScrollBar", empty_icon); + theme->set_icon("decrement_pressed", "VScrollBar", empty_icon); // HSlider @@ -562,10 +554,9 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const empty.instantiate(); theme->set_stylebox("bg", "ScrollContainer", empty); - // WindowDialog + // Window - theme->set_stylebox("panel", "Window", default_style); - theme->set_stylebox("window_panel", "Window", sb_expand(make_stylebox(popup_window_png, 10, 26, 10, 8), 8, 24, 8, 6)); + theme->set_stylebox("embedded_border", "Window", sb_expand(make_stylebox(popup_window_png, 10, 26, 10, 8), 8, 24, 8, 6)); theme->set_constant("scaleborder_size", "Window", 4 * scale); theme->set_font("title_font", "Window", large_font); @@ -579,10 +570,14 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("resize_margin", "Window", 4 * scale); theme->set_icon("close", "Window", make_icon(close_png)); - theme->set_icon("close_highlight", "Window", make_icon(close_hl_png)); + theme->set_icon("close_pressed", "Window", make_icon(close_hl_png)); theme->set_constant("close_h_ofs", "Window", 18 * scale); theme->set_constant("close_v_ofs", "Window", 18 * scale); + // AcceptDialog + + theme->set_stylebox("panel", "AcceptDialog", make_stylebox(dialog_bg_png, 0, 0, 0, 0)); + // File Dialog theme->set_icon("parent_folder", "FileDialog", make_icon(icon_parent_folder_png)); @@ -693,6 +688,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("checked", "Tree", make_icon(checked_png)); theme->set_icon("unchecked", "Tree", make_icon(unchecked_png)); + theme->set_icon("indeterminate", "Tree", make_icon(indeterminate_png)); theme->set_icon("updown", "Tree", make_icon(updown_png)); theme->set_icon("select_arrow", "Tree", make_icon(dropdown_png)); theme->set_icon("arrow", "Tree", make_icon(arrow_down_png)); @@ -847,12 +843,42 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("add_preset", "ColorPicker", make_icon(icon_add_png)); theme->set_icon("color_hue", "ColorPicker", make_icon(color_picker_hue_png)); theme->set_icon("color_sample", "ColorPicker", make_icon(color_picker_sample_png)); - theme->set_icon("preset_bg", "ColorPicker", make_icon(mini_checkerboard_png)); + theme->set_icon("sample_bg", "ColorPicker", make_icon(mini_checkerboard_png)); theme->set_icon("overbright_indicator", "ColorPicker", make_icon(overbright_indicator_png)); theme->set_icon("bar_arrow", "ColorPicker", make_icon(bar_arrow_png)); theme->set_icon("picker_cursor", "ColorPicker", make_icon(picker_cursor_png)); + // ColorPickerButton + theme->set_icon("bg", "ColorPickerButton", make_icon(mini_checkerboard_png)); + theme->set_stylebox("normal", "ColorPickerButton", sb_button_normal); + theme->set_stylebox("pressed", "ColorPickerButton", sb_button_pressed); + theme->set_stylebox("hover", "ColorPickerButton", sb_button_hover); + theme->set_stylebox("disabled", "ColorPickerButton", sb_button_disabled); + theme->set_stylebox("focus", "ColorPickerButton", sb_button_focus); + + theme->set_font("font", "ColorPickerButton", Ref<Font>()); + theme->set_font_size("font_size", "ColorPickerButton", -1); + + theme->set_color("font_color", "ColorPickerButton", Color(1, 1, 1, 1)); + theme->set_color("font_pressed_color", "ColorPickerButton", Color(0.8, 0.8, 0.8, 1)); + theme->set_color("font_hover_color", "ColorPickerButton", Color(1, 1, 1, 1)); + 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("outline_size", "ColorPickerButton", 0); + + // ColorPresetButton + + Ref<StyleBoxFlat> preset_sb = make_flat_stylebox(Color(1, 1, 1), 2, 2, 2, 2); + preset_sb->set_corner_radius_all(2); + preset_sb->set_corner_detail(2); + preset_sb->set_anti_aliased(false); + + theme->set_stylebox("preset_fg", "ColorPresetButton", preset_sb); + theme->set_icon("preset_bg", "ColorPresetButton", make_icon(mini_checkerboard_png)); + theme->set_icon("overbright_indicator", "ColorPresetButton", make_icon(overbright_indicator_png)); // TooltipPanel @@ -903,7 +929,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("shadow_offset_y", "RichTextLabel", 1 * scale); theme->set_constant("shadow_as_outline", "RichTextLabel", 0 * scale); - theme->set_constant("line_separation", "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); @@ -942,6 +968,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("more", "GraphEdit", make_icon(icon_zoom_more_png)); theme->set_icon("snap", "GraphEdit", make_icon(icon_snap_grid_png)); theme->set_icon("minimap", "GraphEdit", make_icon(icon_grid_minimap_png)); + theme->set_icon("layout", "GraphEdit", make_icon(icon_grid_layout_png)); theme->set_stylebox("bg", "GraphEdit", make_stylebox(tree_bg_png, 4, 4, 4, 5)); theme->set_color("grid_minor", "GraphEdit", Color(1, 1, 1, 0.05)); theme->set_color("grid_major", "GraphEdit", Color(1, 1, 1, 0.2)); @@ -953,7 +980,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const // Visual Node Ports - theme->set_constant("port_grab_distance_horizontal", "GraphEdit", 48 * scale); + theme->set_constant("port_grab_distance_horizontal", "GraphEdit", 24 * scale); theme->set_constant("port_grab_distance_vertical", "GraphEdit", 6 * scale); theme->set_stylebox("bg", "GraphEditMinimap", make_flat_stylebox(Color(0.24, 0.24, 0.24), 0, 0, 0, 0)); @@ -984,7 +1011,7 @@ void make_default_theme(bool p_hidpi, Ref<Font> p_font) { Ref<StyleBox> default_style; Ref<Texture2D> default_icon; Ref<Font> default_font; - int default_font_size = 16; + if (p_font.is_valid()) { // Use the custom font defined in the Project Settings. default_font = p_font; @@ -997,8 +1024,9 @@ void make_default_theme(bool p_hidpi, Ref<Font> p_font) { Ref<FontData> dynamic_font_data; dynamic_font_data.instantiate(); - dynamic_font_data->load_memory(_font_OpenSans_SemiBold, _font_OpenSans_SemiBold_size, "ttf", default_font_size); + dynamic_font_data->set_data_ptr(_font_OpenSans_SemiBold, _font_OpenSans_SemiBold_size); dynamic_font->add_data(dynamic_font_data); + dynamic_font->set_base_size(default_font_size); default_font = dynamic_font; } diff --git a/scene/resources/default_theme/default_theme.h b/scene/resources/default_theme/default_theme.h index a7b2bec5a4..4cd781e814 100644 --- a/scene/resources/default_theme/default_theme.h +++ b/scene/resources/default_theme/default_theme.h @@ -33,6 +33,8 @@ #include "scene/resources/theme.h" +const int default_font_size = 16; + void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const Ref<Font> &large_font, Ref<Texture2D> &default_icon, Ref<StyleBox> &default_style, float p_scale); void make_default_theme(bool p_hidpi, Ref<Font> p_font); void clear_default_theme(); diff --git a/scene/resources/default_theme/dialog_bg.png b/scene/resources/default_theme/dialog_bg.png Binary files differnew file mode 100644 index 0000000000..a23a10b48a --- /dev/null +++ b/scene/resources/default_theme/dialog_bg.png diff --git a/scene/resources/default_theme/icon_grid_layout.png b/scene/resources/default_theme/icon_grid_layout.png Binary files differnew file mode 100644 index 0000000000..a249252a79 --- /dev/null +++ b/scene/resources/default_theme/icon_grid_layout.png diff --git a/scene/resources/default_theme/indeterminate.png b/scene/resources/default_theme/indeterminate.png Binary files differnew file mode 100644 index 0000000000..28a457b251 --- /dev/null +++ b/scene/resources/default_theme/indeterminate.png diff --git a/scene/resources/default_theme/popup_window.png b/scene/resources/default_theme/popup_window.png Binary files differindex 174a29ef45..442084049d 100644 --- a/scene/resources/default_theme/popup_window.png +++ b/scene/resources/default_theme/popup_window.png diff --git a/scene/resources/default_theme/theme_data.h b/scene/resources/default_theme/theme_data.h index 7d747e3c9e..6a556c1112 100644 --- a/scene/resources/default_theme/theme_data.h +++ b/scene/resources/default_theme/theme_data.h @@ -50,10 +50,6 @@ static const unsigned char checked_disabled_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x3, 0x0, 0x0, 0x0, 0x28, 0x2d, 0xf, 0x53, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xb, 0x13, 0x0, 0x0, 0xb, 0x13, 0x1, 0x0, 0x9a, 0x9c, 0x18, 0x0, 0x0, 0x0, 0x99, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x7a, 0x78, 0x83, 0x7a, 0x78, 0x83, 0x73, 0x72, 0x7b, 0x7a, 0x78, 0x83, 0x7a, 0x78, 0x83, 0x7a, 0x78, 0x83, 0x82, 0x80, 0x8a, 0x90, 0x90, 0x93, 0x6a, 0x69, 0x70, 0x6a, 0x68, 0x70, 0x93, 0x93, 0x95, 0x58, 0x58, 0x5c, 0x58, 0x58, 0x5b, 0x7d, 0x7d, 0x7f, 0x58, 0x58, 0x5b, 0xa4, 0xa4, 0xa4, 0x9e, 0x9e, 0xa0, 0x9e, 0x9e, 0x9e, 0x9b, 0x9b, 0x9c, 0x9b, 0x9b, 0x9b, 0x9a, 0x9a, 0x9a, 0x99, 0x99, 0x99, 0x98, 0x98, 0x98, 0x97, 0x97, 0x97, 0x96, 0x96, 0x96, 0x95, 0x95, 0x95, 0x93, 0x93, 0x94, 0x8f, 0x8f, 0x8f, 0x86, 0x86, 0x88, 0x85, 0x85, 0x86, 0x82, 0x82, 0x83, 0x81, 0x81, 0x83, 0x7f, 0x7f, 0x81, 0x7c, 0x7c, 0x7e, 0x7a, 0x7a, 0x7d, 0x78, 0x78, 0x7b, 0x71, 0x71, 0x74, 0x68, 0x68, 0x6c, 0x66, 0x66, 0x6a, 0x65, 0x65, 0x68, 0x63, 0x63, 0x66, 0x5f, 0x5f, 0x63, 0x5c, 0x5c, 0x60, 0x5c, 0x5c, 0x5f, 0x5a, 0x5a, 0x5e, 0x59, 0x59, 0x5d, 0x59, 0x59, 0x5c, 0x58, 0x58, 0x5b, 0x57, 0x57, 0x5a, 0x56, 0x56, 0x59, 0x10, 0x13, 0xbb, 0xf, 0x0, 0x0, 0x0, 0x10, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x7, 0x27, 0x27, 0x50, 0x66, 0x68, 0x6a, 0x81, 0xb4, 0xb4, 0xdd, 0xfa, 0xfa, 0xfb, 0xfb, 0x5b, 0xd1, 0xf1, 0xe6, 0x0, 0x0, 0x0, 0x96, 0x49, 0x44, 0x41, 0x54, 0x78, 0x5e, 0x5d, 0x8f, 0xc9, 0x12, 0x82, 0x30, 0x14, 0x4, 0x87, 0x18, 0x50, 0x51, 0x44, 0x25, 0x42, 0x4, 0x77, 0xc4, 0x8d, 0x97, 0x0, 0xf9, 0xff, 0x8f, 0xb3, 0x88, 0xa4, 0x4a, 0xed, 0x63, 0x5f, 0xa6, 0x7, 0xf8, 0x7, 0x1e, 0xe3, 0x7e, 0x60, 0x19, 0x4f, 0x46, 0x1e, 0x0, 0x36, 0x8d, 0x4c, 0x67, 0x59, 0x65, 0x33, 0x6, 0x80, 0x47, 0xad, 0x56, 0x3d, 0xb7, 0x3c, 0x5d, 0x70, 0x0, 0xbe, 0xd1, 0x44, 0x65, 0x4d, 0x94, 0xc8, 0xc2, 0xf8, 0x0, 0x82, 0x4e, 0x91, 0x94, 0x15, 0x5d, 0xd2, 0xec, 0xde, 0x5, 0x83, 0x38, 0xc8, 0xe3, 0x63, 0x23, 0xce, 0xca, 0x9, 0x7a, 0x6e, 0xf3, 0x93, 0x48, 0x1a, 0x27, 0x14, 0x35, 0x3b, 0xb9, 0x5e, 0x56, 0xe4, 0x84, 0x22, 0xba, 0xa, 0x51, 0xd0, 0xb7, 0xa8, 0xcb, 0xfd, 0xcb, 0x9, 0x3b, 0xfb, 0x41, 0xdb, 0x59, 0x17, 0xa6, 0x94, 0x6e, 0xe3, 0x3e, 0x8c, 0x85, 0xf1, 0x90, 0x6e, 0xe6, 0x21, 0xfb, 0x39, 0xe7, 0x73, 0xe6, 0xfd, 0x5f, 0x7, 0xde, 0xc3, 0xb5, 0x16, 0x87, 0xb0, 0x9e, 0x42, 0x46, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; -static const unsigned char checker_bg_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x8, 0x8, 0x0, 0x0, 0x0, 0x0, 0xe1, 0x64, 0xe1, 0x57, 0x0, 0x0, 0x0, 0x14, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0xfc, 0xcf, 0xc0, 0xc0, 0xd0, 0x0, 0xc4, 0xf8, 0x18, 0xf5, 0x84, 0x19, 0x0, 0x9f, 0x5f, 0xa, 0x1, 0xf8, 0xef, 0x65, 0xf4, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 -}; - static const unsigned char close_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x0, 0x62, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0xa0, 0x16, 0xe0, 0x8c, 0xe0, 0x11, 0x43, 0xe6, 0xf3, 0x88, 0x71, 0x46, 0xa0, 0x48, 0x73, 0xfc, 0xe3, 0xb8, 0xcc, 0x23, 0x86, 0x90, 0xe6, 0xb8, 0xcc, 0xf1, 0xf, 0x49, 0x9, 0x8f, 0x28, 0xe7, 0x25, 0x8e, 0xff, 0x1c, 0xd7, 0xb9, 0x24, 0x91, 0x79, 0xdc, 0x12, 0x40, 0xe, 0xa6, 0x12, 0x54, 0x69, 0x4c, 0x25, 0xb7, 0x38, 0xae, 0x21, 0xa4, 0x31, 0x94, 0x80, 0x24, 0x81, 0xf0, 0x36, 0x48, 0x1a, 0xaf, 0x2, 0x88, 0x5b, 0xf0, 0x5a, 0x81, 0xa1, 0x4, 0xe1, 0x34, 0x84, 0x73, 0xb1, 0x4a, 0xa3, 0x7b, 0x9a, 0x70, 0x40, 0x11, 0xe, 0x6a, 0xca, 0x1, 0x0, 0x2a, 0x28, 0x37, 0x83, 0x3e, 0x27, 0xb0, 0x34, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; @@ -70,6 +66,10 @@ static const unsigned char color_picker_sample_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x14, 0x8, 0x0, 0x0, 0x0, 0x0, 0x47, 0x29, 0xbc, 0x83, 0x0, 0x0, 0x0, 0x3c, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xed, 0xd5, 0x21, 0x11, 0x0, 0x30, 0xc, 0x4, 0xc1, 0xfa, 0x57, 0x53, 0x87, 0xed, 0x4, 0x45, 0xc4, 0xed, 0xa3, 0xc3, 0x4b, 0xfe, 0xbc, 0xd9, 0x9d, 0x35, 0x2b, 0xe, 0x0, 0x0, 0x0, 0x80, 0xed, 0x66, 0xc5, 0x1, 0x0, 0x0, 0x0, 0xe0, 0x6, 0x1, 0x0, 0x0, 0x90, 0x6, 0x70, 0x83, 0x0, 0x0, 0x0, 0x28, 0x3, 0x7c, 0x54, 0x93, 0xd6, 0xf1, 0xd1, 0x16, 0x8a, 0x17, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; +static const unsigned char dialog_bg_png[] = { + 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x16, 0x0, 0x0, 0x0, 0x26, 0x8, 0x3, 0x0, 0x0, 0x0, 0xf7, 0x10, 0x9b, 0xa4, 0x0, 0x0, 0x2, 0xf5, 0x7a, 0x54, 0x58, 0x74, 0x52, 0x61, 0x77, 0x20, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x65, 0x78, 0x69, 0x66, 0x0, 0x0, 0x78, 0xda, 0xb5, 0x95, 0x6d, 0x72, 0xe4, 0x28, 0xc, 0x86, 0xff, 0x73, 0x8a, 0x3d, 0x2, 0xfa, 0x42, 0x70, 0x1c, 0xc, 0xa6, 0x6a, 0x6f, 0xb0, 0xc7, 0x9f, 0x17, 0xec, 0x38, 0xd9, 0x4c, 0x57, 0xda, 0x49, 0xcf, 0x40, 0xb5, 0xa5, 0x92, 0x5, 0x12, 0x3c, 0x92, 0x3b, 0xec, 0xff, 0xfd, 0x3b, 0xc2, 0x3f, 0x18, 0xa4, 0x39, 0x6, 0x35, 0xcf, 0xa9, 0xa4, 0x14, 0x31, 0xb4, 0x68, 0xe1, 0xa, 0x25, 0xc7, 0xf7, 0xb1, 0x9f, 0xf2, 0xb0, 0x51, 0xd4, 0xf5, 0x5c, 0xa3, 0xe8, 0xf9, 0x8e, 0x22, 0x85, 0x8f, 0x2f, 0x2e, 0x8d, 0x21, 0x5, 0x52, 0xe, 0xa3, 0xbf, 0x2d, 0x90, 0xd3, 0xfe, 0xe6, 0x9f, 0x2e, 0x89, 0x8d, 0x1e, 0xbc, 0x20, 0xfb, 0xb4, 0x40, 0xae, 0x30, 0xfc, 0x31, 0xb0, 0xd7, 0xd3, 0xce, 0x91, 0xff, 0x97, 0x91, 0x7a, 0xb4, 0xf8, 0x71, 0xe4, 0xf7, 0xdf, 0x18, 0x3d, 0x8f, 0xb1, 0x1f, 0xa7, 0xab, 0x9a, 0x70, 0xd, 0xe9, 0x38, 0xd4, 0x11, 0x22, 0xbc, 0x6d, 0x3, 0xc7, 0xd, 0x5b, 0xc9, 0x5a, 0x96, 0x30, 0x1d, 0x3f, 0x83, 0xee, 0x6b, 0x16, 0xcc, 0x1c, 0x6b, 0x6c, 0xa4, 0xb1, 0xc7, 0x16, 0x37, 0xcc, 0x46, 0x85, 0x98, 0x24, 0xe, 0x52, 0xea, 0x81, 0x2a, 0xd, 0xda, 0xa9, 0x43, 0x36, 0x6a, 0xc8, 0x51, 0x79, 0x67, 0x87, 0x64, 0x6e, 0x2c, 0xcb, 0x96, 0xc5, 0xb9, 0x70, 0x93, 0x28, 0x24, 0x3a, 0x27, 0xd, 0x76, 0x29, 0xd2, 0x25, 0xb, 0x4b, 0xe3, 0x5d, 0x44, 0x34, 0x8, 0x5f, 0xb9, 0xd0, 0x8a, 0x5b, 0x56, 0xbc, 0x46, 0x19, 0x91, 0x3b, 0xc1, 0x95, 0x9, 0x9b, 0x11, 0x96, 0x7c, 0x39, 0xc3, 0x33, 0x87, 0x3b, 0x73, 0x8c, 0x16, 0x71, 0x47, 0x44, 0x38, 0x3d, 0x9d, 0x75, 0x81, 0xbc, 0x98, 0x27, 0x7, 0x9a, 0xd7, 0x28, 0xf3, 0x9, 0x37, 0x0, 0xa1, 0x71, 0x72, 0xb3, 0x75, 0xc1, 0x6f, 0xf3, 0x1a, 0xe1, 0x3, 0x58, 0x1, 0x41, 0x5b, 0xd7, 0x9c, 0x71, 0xc0, 0x1a, 0xb7, 0x63, 0x8b, 0xcd, 0xe8, 0xbd, 0xb6, 0x64, 0x15, 0x80, 0xc0, 0xcf, 0x20, 0x8f, 0xfa, 0x22, 0xef, 0x93, 0x1a, 0xaf, 0x2a, 0x51, 0xc4, 0x36, 0x24, 0x43, 0x2, 0x4, 0x31, 0x91, 0x18, 0x25, 0x8a, 0xce, 0xec, 0x44, 0x2a, 0x9c, 0x1, 0xa8, 0x22, 0x73, 0x16, 0xe5, 0xd, 0x4, 0xc8, 0x8c, 0x3b, 0x92, 0x64, 0x15, 0x49, 0x60, 0x93, 0x51, 0x47, 0x88, 0x8d, 0x35, 0x4e, 0xcb, 0x97, 0x8d, 0xf, 0x3b, 0x5a, 0x5, 0x7c, 0x4c, 0x92, 0x38, 0xd8, 0x14, 0xa9, 0x80, 0xa5, 0x6a, 0xa8, 0x1f, 0xd7, 0x8c, 0x1a, 0xaa, 0x26, 0xa6, 0x66, 0x96, 0xcc, 0x2d, 0x5b, 0xb1, 0x1a, 0x92, 0x24, 0x4d, 0x96, 0x52, 0xf2, 0x34, 0x7b, 0xae, 0xba, 0xb8, 0xba, 0x79, 0x72, 0xf7, 0xec, 0xc5, 0x6b, 0x96, 0xac, 0xd9, 0x72, 0xca, 0x9e, 0x73, 0x2e, 0xb9, 0x16, 0x2e, 0x82, 0x96, 0xb4, 0x92, 0x8a, 0x97, 0x5c, 0x4a, 0xa9, 0x15, 0x31, 0xab, 0x86, 0x6a, 0x15, 0xab, 0x2b, 0x3c, 0x6a, 0xdd, 0x78, 0x93, 0x4d, 0x37, 0xdb, 0xd2, 0xe6, 0x5b, 0xde, 0xca, 0x56, 0x1b, 0xca, 0xa7, 0x69, 0xb3, 0x96, 0x9a, 0xb7, 0xdc, 0x4a, 0xab, 0x9d, 0xbb, 0x74, 0xed, 0xd6, 0x53, 0xf7, 0x9e, 0x7b, 0xe9, 0x75, 0xa7, 0x1d, 0xa5, 0x14, 0x76, 0xdd, 0x6d, 0x4f, 0xbb, 0xef, 0x79, 0x2f, 0x7b, 0x1d, 0xa8, 0xb5, 0x21, 0x43, 0x87, 0x8d, 0x34, 0x7c, 0xe4, 0x51, 0x46, 0xbd, 0xa8, 0xd1, 0xd9, 0xb6, 0x9f, 0xe7, 0x37, 0xa8, 0xd1, 0x49, 0x8d, 0x17, 0xa9, 0xe9, 0xe7, 0x17, 0x35, 0x58, 0xdd, 0xe7, 0x46, 0x6b, 0xb, 0x9a, 0xdf, 0x19, 0x9b, 0xcc, 0x40, 0x8c, 0x95, 0x40, 0xdc, 0x27, 0x1, 0x14, 0x34, 0x4f, 0x66, 0x31, 0x93, 0x2a, 0x4f, 0x72, 0x93, 0x59, 0x2c, 0x8c, 0xae, 0x30, 0x46, 0x92, 0x36, 0xd9, 0x74, 0x8a, 0x35, 0x50, 0x2, 0x42, 0xdd, 0x89, 0x6d, 0xd0, 0xc5, 0xee, 0x9d, 0xdc, 0x6d, 0x6e, 0x1, 0x77, 0xfd, 0x8c, 0x1b, 0xdf, 0x21, 0x17, 0x26, 0xba, 0x3f, 0x40, 0x8e, 0xc3, 0x2e, 0x9f, 0xb8, 0x3d, 0xa0, 0xd6, 0xe7, 0x97, 0xb0, 0x2d, 0x62, 0x47, 0x17, 0xce, 0x3b, 0x8d, 0x82, 0xee, 0x83, 0x6f, 0xe5, 0x5c, 0x79, 0x53, 0x50, 0x5b, 0x5a, 0x7e, 0x55, 0xfe, 0x85, 0x8d, 0x68, 0x9, 0x1c, 0xe2, 0x67, 0x32, 0x3c, 0x7c, 0xa1, 0x6b, 0xe7, 0xf4, 0x1d, 0x25, 0xdc, 0x70, 0x42, 0xce, 0x65, 0x85, 0xb0, 0x2f, 0x94, 0x70, 0xc7, 0xe9, 0x37, 0xe5, 0x41, 0xd2, 0xe1, 0x47, 0xe7, 0x78, 0x90, 0x74, 0x40, 0x91, 0x8a, 0xa6, 0x74, 0x1a, 0x5b, 0xa7, 0x72, 0x86, 0xfe, 0xa6, 0x39, 0xdc, 0x72, 0x47, 0x97, 0x1b, 0xa1, 0xbf, 0xf0, 0x3f, 0x8c, 0xe, 0x7d, 0xa8, 0x84, 0x2f, 0xde, 0x7d, 0xad, 0x90, 0x1a, 0xdb, 0xc, 0xb1, 0x2d, 0x25, 0x5c, 0xda, 0x8b, 0x4a, 0x38, 0xb3, 0x2e, 0x15, 0xa7, 0x39, 0x8c, 0x38, 0x53, 0xeb, 0x67, 0xe8, 0xfb, 0xe6, 0x70, 0xdb, 0xfd, 0x49, 0xa9, 0x86, 0x17, 0xa9, 0x5f, 0x5c, 0xc2, 0x8b, 0xd4, 0x2f, 0x73, 0x78, 0x4e, 0xdd, 0x6e, 0xc9, 0x70, 0xd7, 0xf1, 0x59, 0x3d, 0x84, 0x17, 0xca, 0x88, 0x3e, 0xca, 0xf0, 0xd9, 0xf0, 0x53, 0xf9, 0xe7, 0x36, 0xf2, 0x81, 0x8f, 0x7b, 0x81, 0xf2, 0xb, 0x12, 0xea, 0xac, 0x15, 0x79, 0x70, 0x44, 0x63, 0x0, 0x0, 0x1, 0x68, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0xe8, 0xe5, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0x1d, 0x22, 0x0, 0x0, 0x0, 0x1a, 0x19, 0x1c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x1e, 0x23, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0x1d, 0x21, 0x17, 0x16, 0x19, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x21, 0x1f, 0x24, 0x1b, 0x1a, 0x1d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x21, 0x1f, 0x24, 0x1e, 0x1c, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x22, 0x20, 0x25, 0x20, 0x1e, 0x23, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x21, 0x1f, 0x24, 0x0, 0x0, 0x0, 0x21, 0x1f, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x22, 0x20, 0x25, 0x0, 0x0, 0x0, 0x20, 0x20, 0x25, 0x20, 0x1d, 0x25, 0x20, 0x1d, 0x22, 0x1d, 0x1d, 0x22, 0x1d, 0x1d, 0x20, 0x1d, 0x1a, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x32, 0x30, 0x38, 0xe8, 0xe5, 0xf1, 0xe5, 0xe2, 0xeb, 0xe3, 0xe1, 0xe8, 0xe1, 0xdf, 0xe7, 0xe0, 0xde, 0xe6, 0xdf, 0xdd, 0xe5, 0xde, 0xdc, 0xe4, 0xdd, 0xdb, 0xe3, 0xdc, 0xda, 0xe2, 0xda, 0xd8, 0xe0, 0xd9, 0xd7, 0xdf, 0xd7, 0xd6, 0xdf, 0xd6, 0xd4, 0xdd, 0xd5, 0xd3, 0xdc, 0xd4, 0xd1, 0xdb, 0xd3, 0xd0, 0xda, 0xd1, 0xce, 0xd8, 0xd0, 0xcd, 0xd7, 0xcf, 0xcd, 0xd7, 0xe2, 0xdf, 0xeb, 0x48, 0x46, 0x51, 0x42, 0x40, 0x4b, 0x40, 0x3e, 0x48, 0x40, 0x3d, 0x48, 0x48, 0x45, 0x50, 0x42, 0x3f, 0x4a, 0x3f, 0x3d, 0x48, 0x47, 0x44, 0x50, 0x41, 0x3f, 0x4a, 0x3f, 0x3d, 0x47, 0x41, 0x3e, 0x49, 0x3f, 0x3c, 0x47, 0x46, 0x43, 0x4f, 0x3e, 0x3c, 0x46, 0x40, 0x3e, 0x49, 0x3d, 0x3b, 0x46, 0x45, 0x43, 0x4e, 0x3d, 0x3b, 0x45, 0x44, 0x42, 0x4d, 0x3d, 0x3a, 0x45, 0x3e, 0x3c, 0x47, 0x3c, 0x3a, 0x44, 0x43, 0x42, 0x4c, 0x43, 0x40, 0x4c, 0x3e, 0x3b, 0x46, 0x3b, 0x39, 0x43, 0x43, 0x3f, 0x4c, 0x43, 0x3f, 0x4b, 0x3a, 0x38, 0x42, 0x42, 0x3e, 0x4b, 0x42, 0x3e, 0x49, 0x3a, 0x37, 0x41, 0x39, 0x37, 0x41, 0x3f, 0x3e, 0x48, 0x39, 0x37, 0x40, 0x38, 0x36, 0x40, 0x3e, 0x3d, 0x48, 0x38, 0x36, 0x3f, 0x3e, 0x3d, 0x47, 0x3a, 0x38, 0x41, 0x38, 0x35, 0x3f, 0x37, 0x35, 0x3e, 0x39, 0x36, 0x40, 0x37, 0x34, 0x3e, 0x3d, 0x3a, 0x46, 0x36, 0x34, 0x3d, 0x3d, 0x3a, 0x44, 0x37, 0x35, 0x3f, 0x35, 0x33, 0x3c, 0x6b, 0xff, 0x8f, 0xb1, 0x0, 0x0, 0x0, 0x1, 0x62, 0x4b, 0x47, 0x44, 0x0, 0x88, 0x5, 0x1d, 0x48, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xb, 0x13, 0x0, 0x0, 0xb, 0x13, 0x1, 0x0, 0x9a, 0x9c, 0x18, 0x0, 0x0, 0x0, 0x7, 0x74, 0x49, 0x4d, 0x45, 0x7, 0xe5, 0x7, 0x1b, 0x17, 0x11, 0x18, 0xe6, 0xb9, 0x22, 0xac, 0x0, 0x0, 0x0, 0x3f, 0x49, 0x44, 0x41, 0x54, 0x28, 0xcf, 0x63, 0xf4, 0x64, 0xc0, 0x6, 0x98, 0x18, 0x68, 0x27, 0xcc, 0xc2, 0x88, 0x5d, 0x98, 0x99, 0x24, 0x61, 0x26, 0x52, 0x84, 0x71, 0xb9, 0x84, 0x89, 0x76, 0xc2, 0xb8, 0xac, 0x64, 0x21, 0x49, 0x35, 0x33, 0x15, 0x54, 0xe3, 0xa, 0x6f, 0x26, 0x6a, 0xa8, 0x26, 0xc9, 0x4a, 0xaa, 0x44, 0x3, 0x89, 0x89, 0x8d, 0x99, 0xee, 0xe9, 0x1b, 0x87, 0x30, 0x0, 0x20, 0x4, 0x0, 0xed, 0x48, 0xa7, 0x26, 0x6c, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 +}; + static const unsigned char dropdown_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x8, 0x8, 0x4, 0x0, 0x0, 0x0, 0x6e, 0x6, 0x76, 0x0, 0x0, 0x0, 0x0, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x60, 0xf8, 0xc0, 0xcc, 0x0, 0x2, 0x60, 0x16, 0x98, 0x78, 0x67, 0x8, 0x81, 0x6f, 0x4d, 0xde, 0x9a, 0x0, 0x5, 0xde, 0x3a, 0x3d, 0xfc, 0x8f, 0x80, 0xaf, 0xba, 0x18, 0xde, 0x29, 0x2, 0x19, 0xbf, 0x61, 0x2, 0x6f, 0x62, 0x18, 0x3e, 0xb0, 0xbd, 0x97, 0x4, 0x32, 0xff, 0x80, 0xb9, 0xb1, 0x20, 0x93, 0xc0, 0x42, 0x8, 0x2e, 0x54, 0xe8, 0x9d, 0xdc, 0x9b, 0x54, 0x10, 0xb, 0x21, 0xc4, 0x4, 0x63, 0x1, 0x0, 0x86, 0x1f, 0x3b, 0x1e, 0x92, 0x22, 0x3f, 0x40, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; @@ -178,6 +178,10 @@ static const unsigned char icon_folder_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x0, 0x2e, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0xa0, 0x6, 0x78, 0x70, 0xf4, 0xc1, 0x7f, 0x24, 0x78, 0x18, 0x53, 0xc1, 0x7f, 0x54, 0x48, 0x50, 0xc1, 0x43, 0x1b, 0xbc, 0xa, 0x50, 0xad, 0x23, 0xa4, 0xe0, 0xff, 0x70, 0x52, 0x70, 0x18, 0x97, 0xf4, 0xfd, 0x43, 0xd4, 0x88, 0x4a, 0x0, 0x5a, 0xcb, 0x18, 0xab, 0x5e, 0xd9, 0x1a, 0x53, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; +static const unsigned char icon_grid_layout_png[] = { + 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x6, 0x0, 0x0, 0x0, 0x1f, 0xf3, 0xff, 0x61, 0x0, 0x0, 0x5, 0x52, 0x69, 0x54, 0x58, 0x74, 0x58, 0x4d, 0x4c, 0x3a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x78, 0x6d, 0x70, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x3d, 0x22, 0xef, 0xbb, 0xbf, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x57, 0x35, 0x4d, 0x30, 0x4d, 0x70, 0x43, 0x65, 0x68, 0x69, 0x48, 0x7a, 0x72, 0x65, 0x53, 0x7a, 0x4e, 0x54, 0x63, 0x7a, 0x6b, 0x63, 0x39, 0x64, 0x22, 0x3f, 0x3e, 0xa, 0x3c, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x3d, 0x22, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x3a, 0x6e, 0x73, 0x3a, 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x22, 0x20, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x74, 0x6b, 0x3d, 0x22, 0x58, 0x4d, 0x50, 0x20, 0x43, 0x6f, 0x72, 0x65, 0x20, 0x35, 0x2e, 0x35, 0x2e, 0x30, 0x22, 0x3e, 0xa, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x72, 0x64, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x31, 0x39, 0x39, 0x39, 0x2f, 0x30, 0x32, 0x2f, 0x32, 0x32, 0x2d, 0x72, 0x64, 0x66, 0x2d, 0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x2d, 0x6e, 0x73, 0x23, 0x22, 0x3e, 0xa, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x72, 0x64, 0x66, 0x3a, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x22, 0x22, 0xa, 0x20, 0x20, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x64, 0x63, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x70, 0x75, 0x72, 0x6c, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x64, 0x63, 0x2f, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x31, 0x2e, 0x31, 0x2f, 0x22, 0xa, 0x20, 0x20, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x65, 0x78, 0x69, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x78, 0x69, 0x66, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x22, 0xa, 0x20, 0x20, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x74, 0x69, 0x66, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x69, 0x66, 0x66, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x22, 0xa, 0x20, 0x20, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x22, 0xa, 0x20, 0x20, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x6d, 0x70, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x22, 0xa, 0x20, 0x20, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x6d, 0x6d, 0x2f, 0x22, 0xa, 0x20, 0x20, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x73, 0x74, 0x45, 0x76, 0x74, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x73, 0x54, 0x79, 0x70, 0x65, 0x2f, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x23, 0x22, 0xa, 0x20, 0x20, 0x20, 0x65, 0x78, 0x69, 0x66, 0x3a, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x58, 0x44, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x31, 0x36, 0x22, 0xa, 0x20, 0x20, 0x20, 0x65, 0x78, 0x69, 0x66, 0x3a, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x59, 0x44, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x31, 0x36, 0x22, 0xa, 0x20, 0x20, 0x20, 0x65, 0x78, 0x69, 0x66, 0x3a, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x53, 0x70, 0x61, 0x63, 0x65, 0x3d, 0x22, 0x31, 0x22, 0xa, 0x20, 0x20, 0x20, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x57, 0x69, 0x64, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x36, 0x22, 0xa, 0x20, 0x20, 0x20, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x3d, 0x22, 0x31, 0x36, 0x22, 0xa, 0x20, 0x20, 0x20, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x6e, 0x69, 0x74, 0x3d, 0x22, 0x32, 0x22, 0xa, 0x20, 0x20, 0x20, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x58, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x37, 0x32, 0x2e, 0x30, 0x22, 0xa, 0x20, 0x20, 0x20, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x59, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x37, 0x32, 0x2e, 0x30, 0x22, 0xa, 0x20, 0x20, 0x20, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x3a, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x3d, 0x22, 0x33, 0x22, 0xa, 0x20, 0x20, 0x20, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x3a, 0x49, 0x43, 0x43, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x3d, 0x22, 0x73, 0x52, 0x47, 0x42, 0x20, 0x49, 0x45, 0x43, 0x36, 0x31, 0x39, 0x36, 0x36, 0x2d, 0x32, 0x2e, 0x31, 0x22, 0xa, 0x20, 0x20, 0x20, 0x78, 0x6d, 0x70, 0x3a, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x44, 0x61, 0x74, 0x65, 0x3d, 0x22, 0x32, 0x30, 0x32, 0x31, 0x2d, 0x30, 0x38, 0x2d, 0x31, 0x35, 0x54, 0x30, 0x39, 0x3a, 0x34, 0x31, 0x3a, 0x34, 0x30, 0x2b, 0x30, 0x32, 0x3a, 0x30, 0x30, 0x22, 0xa, 0x20, 0x20, 0x20, 0x78, 0x6d, 0x70, 0x3a, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x44, 0x61, 0x74, 0x65, 0x3d, 0x22, 0x32, 0x30, 0x32, 0x31, 0x2d, 0x30, 0x38, 0x2d, 0x31, 0x35, 0x54, 0x30, 0x39, 0x3a, 0x34, 0x31, 0x3a, 0x34, 0x30, 0x2b, 0x30, 0x32, 0x3a, 0x30, 0x30, 0x22, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x3c, 0x64, 0x63, 0x3a, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x41, 0x6c, 0x74, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x6c, 0x69, 0x20, 0x78, 0x6d, 0x6c, 0x3a, 0x6c, 0x61, 0x6e, 0x67, 0x3d, 0x22, 0x78, 0x2d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22, 0x3e, 0x47, 0x72, 0x69, 0x64, 0x4c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x6c, 0x69, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x41, 0x6c, 0x74, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x64, 0x63, 0x3a, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x3c, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3a, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x53, 0x65, 0x71, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x6c, 0x69, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x45, 0x76, 0x74, 0x3a, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x64, 0x22, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x45, 0x76, 0x74, 0x3a, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x3d, 0x22, 0x41, 0x66, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x79, 0x20, 0x44, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x20, 0x31, 0x2e, 0x39, 0x2e, 0x31, 0x22, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x45, 0x76, 0x74, 0x3a, 0x77, 0x68, 0x65, 0x6e, 0x3d, 0x22, 0x32, 0x30, 0x32, 0x31, 0x2d, 0x30, 0x38, 0x2d, 0x31, 0x35, 0x54, 0x30, 0x39, 0x3a, 0x34, 0x31, 0x3a, 0x34, 0x30, 0x2b, 0x30, 0x32, 0x3a, 0x30, 0x30, 0x22, 0x2f, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x53, 0x65, 0x71, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x78, 0x6d, 0x70, 0x4d, 0x4d, 0x3a, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x3e, 0xa, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0xa, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x3e, 0xa, 0x3c, 0x2f, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x3e, 0xa, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x65, 0x6e, 0x64, 0x3d, 0x22, 0x72, 0x22, 0x3f, 0x3e, 0x10, 0xfa, 0x51, 0xae, 0x0, 0x0, 0x1, 0x82, 0x69, 0x43, 0x43, 0x50, 0x73, 0x52, 0x47, 0x42, 0x20, 0x49, 0x45, 0x43, 0x36, 0x31, 0x39, 0x36, 0x36, 0x2d, 0x32, 0x2e, 0x31, 0x0, 0x0, 0x28, 0x91, 0x75, 0x91, 0xbf, 0x4b, 0x42, 0x51, 0x14, 0xc7, 0x3f, 0x5a, 0xa1, 0x94, 0x61, 0x50, 0x43, 0x43, 0x83, 0x84, 0x45, 0x83, 0x85, 0x15, 0x84, 0x2d, 0xd, 0x4a, 0xbf, 0xa0, 0x1a, 0xd4, 0x20, 0xab, 0x45, 0x9f, 0xbf, 0x2, 0xb5, 0xc7, 0x7b, 0x4a, 0x48, 0x6b, 0xd0, 0x2a, 0x14, 0x44, 0x2d, 0xfd, 0x1a, 0xea, 0x2f, 0xa8, 0x35, 0x68, 0xe, 0x82, 0xa2, 0x8, 0xa2, 0x2d, 0x68, 0x2e, 0x6a, 0xa9, 0x78, 0x9d, 0xa7, 0x82, 0x11, 0x79, 0x2e, 0xe7, 0x9e, 0xcf, 0xfd, 0xde, 0x7b, 0xe, 0xf7, 0x9e, 0xb, 0xd6, 0x70, 0x46, 0xc9, 0xea, 0x8d, 0x5e, 0xc8, 0xe6, 0xf2, 0x5a, 0x70, 0xd2, 0xef, 0x5a, 0x88, 0x2c, 0xba, 0x6c, 0xcf, 0xd8, 0x71, 0x62, 0xa3, 0xf, 0x5f, 0x54, 0xd1, 0xd5, 0xd9, 0xd0, 0x44, 0x98, 0xba, 0xf6, 0x71, 0x87, 0xc5, 0x8c, 0x37, 0xfd, 0x66, 0xad, 0xfa, 0xe7, 0xfe, 0xb5, 0x96, 0x78, 0x42, 0x57, 0xc0, 0x62, 0x17, 0x1e, 0x53, 0x54, 0x2d, 0x2f, 0x3c, 0x25, 0x3c, 0xb3, 0x96, 0x57, 0x4d, 0xde, 0x16, 0xee, 0x50, 0xd2, 0xd1, 0xb8, 0xf0, 0xa9, 0xb0, 0x47, 0x93, 0xb, 0xa, 0xdf, 0x9a, 0x7a, 0xac, 0xc2, 0x2f, 0x26, 0xa7, 0x2a, 0xfc, 0x65, 0xb2, 0x16, 0xe, 0x6, 0xc0, 0xda, 0x26, 0xec, 0x4a, 0xfd, 0xe2, 0xd8, 0x2f, 0x56, 0xd2, 0x5a, 0x56, 0x58, 0x5e, 0x8e, 0x3b, 0x9b, 0x29, 0x28, 0xd5, 0xfb, 0x98, 0x2f, 0x71, 0x24, 0x72, 0xf3, 0x21, 0x89, 0xdd, 0xe2, 0x5d, 0xe8, 0x4, 0x99, 0xc4, 0x8f, 0x8b, 0x69, 0xc6, 0x9, 0x30, 0xc2, 0x20, 0xa3, 0x32, 0x8f, 0xd0, 0xcf, 0x10, 0x3, 0xb2, 0xa2, 0x4e, 0xbe, 0xb7, 0x9c, 0x3f, 0xc7, 0xaa, 0xe4, 0x2a, 0x32, 0xab, 0x14, 0xd1, 0x58, 0x21, 0x45, 0x9a, 0x3c, 0x1e, 0x51, 0xb, 0x52, 0x3d, 0x21, 0x31, 0x29, 0x7a, 0x42, 0x46, 0x86, 0xa2, 0xd9, 0xff, 0xbf, 0x7d, 0xd5, 0x93, 0xc3, 0x43, 0x95, 0xea, 0xe, 0x3f, 0x34, 0x3d, 0x19, 0xc6, 0x5b, 0xf, 0xd8, 0xb6, 0xe0, 0xbb, 0x64, 0x18, 0x9f, 0x87, 0x86, 0xf1, 0x7d, 0x4, 0xd, 0x8f, 0x70, 0x91, 0xab, 0xe5, 0xaf, 0x1e, 0x80, 0xef, 0x5d, 0xf4, 0x52, 0x4d, 0x73, 0xef, 0x83, 0x73, 0x3, 0xce, 0x2e, 0x6b, 0x5a, 0x6c, 0x7, 0xce, 0x37, 0xa1, 0xf3, 0x41, 0x8d, 0x6a, 0xd1, 0xb2, 0xd4, 0x20, 0x6e, 0x4d, 0x26, 0xe1, 0xf5, 0x4, 0x5a, 0x23, 0xd0, 0x7e, 0xd, 0xcd, 0x4b, 0x95, 0x9e, 0x55, 0xf7, 0x39, 0xbe, 0x87, 0xf0, 0xba, 0x7c, 0xd5, 0x15, 0xec, 0xee, 0x41, 0xaf, 0x9c, 0x77, 0x2e, 0xff, 0x0, 0xa6, 0xc4, 0x68, 0x3, 0x1f, 0xd7, 0x32, 0xd8, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xb, 0x13, 0x0, 0x0, 0xb, 0x13, 0x1, 0x0, 0x9a, 0x9c, 0x18, 0x0, 0x0, 0x1, 0x40, 0x49, 0x44, 0x41, 0x54, 0x38, 0x8d, 0x9d, 0xd2, 0xbd, 0x4a, 0x5d, 0x41, 0x14, 0x86, 0xe1, 0xe7, 0x84, 0xa3, 0x51, 0x22, 0x82, 0x41, 0x8c, 0x12, 0x2, 0xeb, 0x1a, 0x2c, 0x84, 0x4, 0x52, 0x88, 0xb1, 0xd2, 0x26, 0x76, 0x62, 0x67, 0xeb, 0xd, 0x78, 0x19, 0x56, 0x69, 0x82, 0x62, 0x67, 0x25, 0x82, 0x60, 0x97, 0xc2, 0x52, 0x8, 0x24, 0x95, 0x16, 0xa9, 0x6, 0x8c, 0x18, 0xb5, 0x30, 0x88, 0xa7, 0x90, 0x88, 0x5a, 0xec, 0x39, 0x66, 0xd8, 0x7a, 0x54, 0x5c, 0xb0, 0xd8, 0xcc, 0xbb, 0xe7, 0xfb, 0x58, 0x3f, 0xd3, 0x50, 0x8b, 0x94, 0xd2, 0x6b, 0xcc, 0x60, 0x25, 0x22, 0xae, 0xea, 0xff, 0xeb, 0xd1, 0xbc, 0x47, 0xfc, 0xd, 0xbd, 0x58, 0x7e, 0x4c, 0xc, 0x8d, 0x42, 0x3c, 0x82, 0x9f, 0xe8, 0xc1, 0x26, 0xce, 0x3b, 0x68, 0xae, 0xf1, 0x25, 0x22, 0x76, 0xeb, 0x15, 0xf4, 0xa3, 0xf, 0x97, 0x18, 0xce, 0xdf, 0x4e, 0x6, 0xfd, 0x77, 0x2a, 0xc8, 0x55, 0x4, 0xb6, 0xf1, 0x1b, 0x1f, 0x23, 0xe2, 0xfa, 0x49, 0x2d, 0xa4, 0x94, 0x7a, 0x30, 0x98, 0xd9, 0x5b, 0x4c, 0x63, 0x49, 0x35, 0xb, 0x68, 0xe1, 0x2f, 0x46, 0xf0, 0x22, 0xb3, 0x93, 0x88, 0xb8, 0x68, 0x1f, 0x36, 0xb0, 0x9f, 0x73, 0x7, 0x87, 0xf8, 0x53, 0xb0, 0x23, 0x2c, 0xe0, 0xa0, 0x60, 0xeb, 0xa, 0xb7, 0x1, 0xac, 0x60, 0x34, 0x5f, 0x1a, 0x52, 0xcd, 0x67, 0x16, 0x73, 0xe8, 0xca, 0x6c, 0x3f, 0xdf, 0x59, 0xcd, 0x9a, 0x5b, 0x83, 0x67, 0x47, 0x7b, 0xb, 0xa7, 0x98, 0xcf, 0x9, 0xc7, 0xaa, 0x2d, 0xac, 0xe5, 0xf3, 0xbf, 0xcc, 0xde, 0xe1, 0x47, 0x66, 0x5b, 0xa5, 0xc1, 0x67, 0xff, 0x87, 0x78, 0xa5, 0x9a, 0xc1, 0x1a, 0x5e, 0x65, 0xd6, 0xc2, 0x24, 0x3e, 0xe4, 0x36, 0xe0, 0x84, 0xda, 0x1a, 0x1f, 0x8a, 0x94, 0xd2, 0x6, 0xc6, 0x30, 0x1e, 0x11, 0xbf, 0xda, 0xbc, 0x7c, 0x89, 0xc3, 0x58, 0x44, 0x77, 0x7, 0x8f, 0x2e, 0x4c, 0xa9, 0x1e, 0xd1, 0xa7, 0x88, 0xd8, 0x29, 0x5b, 0xa0, 0x7a, 0xc2, 0xf1, 0x80, 0x41, 0x23, 0xe7, 0x4b, 0xbc, 0x79, 0x6a, 0xe5, 0x65, 0xb, 0x5f, 0x53, 0x4a, 0x67, 0x29, 0xa5, 0xf7, 0x25, 0x6f, 0x76, 0x12, 0xdc, 0x13, 0x7b, 0x98, 0x88, 0x88, 0xef, 0x25, 0xbc, 0x1, 0x6c, 0x4d, 0x56, 0x9e, 0x2a, 0x4e, 0x48, 0xae, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 +}; + static const unsigned char icon_grid_minimap_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x6, 0x0, 0x0, 0x0, 0x1f, 0xf3, 0xff, 0x61, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xe, 0xc3, 0x0, 0x0, 0xe, 0xc3, 0x1, 0xc7, 0x6f, 0xa8, 0x64, 0x0, 0x0, 0x0, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x0, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x6e, 0x6b, 0x73, 0x63, 0x61, 0x70, 0x65, 0x2e, 0x6f, 0x72, 0x67, 0x9b, 0xee, 0x3c, 0x1a, 0x0, 0x0, 0x2, 0xd, 0x49, 0x44, 0x41, 0x54, 0x38, 0x8d, 0x75, 0x93, 0x31, 0x68, 0x14, 0x51, 0x10, 0x86, 0xbf, 0xd9, 0xd, 0xbb, 0xde, 0x76, 0x82, 0x21, 0xf8, 0xe0, 0xbc, 0x5d, 0x8b, 0x80, 0x69, 0x6c, 0xd2, 0x5a, 0x6a, 0x91, 0xc3, 0xd2, 0x46, 0x22, 0x8, 0x9, 0x89, 0x70, 0x85, 0x10, 0x41, 0xd, 0x24, 0x45, 0xb0, 0xb, 0x68, 0x11, 0x14, 0x24, 0x10, 0x22, 0x62, 0x21, 0x41, 0xe, 0x4b, 0x21, 0xa4, 0xb7, 0x49, 0x17, 0xb1, 0x8, 0xb9, 0xdd, 0xc7, 0x86, 0x33, 0x21, 0xe1, 0x3a, 0x8f, 0x64, 0x61, 0x6f, 0x2c, 0xbc, 0x3b, 0x36, 0xb9, 0xdc, 0xc0, 0x2b, 0xde, 0xcc, 0xfc, 0xf3, 0xff, 0xfc, 0xcc, 0x48, 0xa3, 0xd1, 0x78, 0x20, 0x22, 0x13, 0xbe, 0xef, 0xaf, 0xdf, 0xac, 0xd7, 0x1f, 0xe1, 0x38, 0xd3, 0xa8, 0x2a, 0xf0, 0x45, 0x6a, 0xb5, 0xcf, 0x5c, 0x11, 0xcd, 0x66, 0x33, 0x38, 0x3f, 0x3f, 0x9f, 0x13, 0x91, 0x7d, 0xb1, 0xd6, 0x6e, 0xaa, 0xea, 0xd3, 0xe0, 0xe8, 0xe8, 0xde, 0xe8, 0xee, 0xee, 0x37, 0xc0, 0xe9, 0xf6, 0x75, 0xf0, 0xfd, 0x9, 0x99, 0x9d, 0x6d, 0x15, 0x81, 0x59, 0x96, 0x3d, 0x3, 0x5e, 0x2, 0x63, 0x22, 0xf2, 0x69, 0xa4, 0x57, 0x1c, 0xdd, 0xdb, 0xfb, 0x5b, 0x0, 0x3, 0x38, 0x67, 0x41, 0x30, 0x11, 0xc7, 0xf1, 0x13, 0x0, 0x11, 0x71, 0xb2, 0x2c, 0x7b, 0xd8, 0xad, 0xad, 0x2, 0x6f, 0xb9, 0x0, 0x38, 0x3c, 0xfc, 0x5, 0x9c, 0xf6, 0xff, 0x22, 0x27, 0x27, 0xe3, 0xe3, 0x7f, 0xa, 0x3, 0x67, 0x45, 0xe4, 0xbb, 0xe7, 0x79, 0xb7, 0xc3, 0x30, 0x7c, 0xd7, 0x67, 0xe9, 0xe3, 0x67, 0x66, 0x5c, 0x60, 0x1, 0x50, 0x40, 0x51, 0x7d, 0x71, 0x6b, 0x72, 0xf2, 0x20, 0x8a, 0xa2, 0xf9, 0x28, 0x8a, 0xe6, 0x1, 0x3a, 0x9d, 0xce, 0x4f, 0x63, 0x4c, 0x3b, 0x4d, 0xd3, 0xd2, 0xc0, 0x80, 0x3c, 0xcf, 0xf, 0x92, 0xa9, 0xa9, 0x31, 0x60, 0x5, 0x58, 0x91, 0x5a, 0xed, 0xc7, 0x15, 0xfe, 0x95, 0xac, 0xb5, 0xcf, 0xf3, 0x3c, 0x3f, 0xe8, 0x25, 0x46, 0xa, 0xc5, 0xd, 0x11, 0x59, 0xb3, 0xd5, 0xea, 0x1b, 0xa0, 0x95, 0x54, 0xab, 0x5b, 0x97, 0xd1, 0x22, 0xb2, 0xa6, 0xaa, 0x6d, 0x60, 0xd, 0x58, 0xba, 0xa0, 0x20, 0xc, 0xc3, 0x65, 0xd7, 0x75, 0x23, 0xe0, 0x2e, 0xb0, 0x1, 0x5c, 0xbf, 0xf4, 0x0, 0xbe, 0xba, 0xae, 0x1b, 0x85, 0x61, 0xb8, 0x3c, 0xa0, 0x20, 0x4d, 0xd3, 0x52, 0xb9, 0x5c, 0x6e, 0xc5, 0x71, 0xbc, 0x23, 0x22, 0xd3, 0x61, 0x18, 0xde, 0x2f, 0xb2, 0x27, 0x49, 0xa2, 0xaa, 0xba, 0x53, 0x2e, 0x97, 0x5b, 0x69, 0x9a, 0x96, 0xf2, 0x3c, 0x1f, 0xf0, 0xc0, 0x5a, 0x6b, 0x5f, 0x1, 0x25, 0x86, 0x84, 0xe3, 0x38, 0x9e, 0xb5, 0x76, 0x2e, 0xcf, 0xf3, 0xfd, 0x1, 0x5, 0x22, 0xb2, 0xa1, 0xaa, 0x4b, 0x22, 0x72, 0xad, 0xcb, 0x38, 0xe0, 0x81, 0xaa, 0x7e, 0x0, 0xce, 0x44, 0xe4, 0xbd, 0xaa, 0xbe, 0xbe, 0xa0, 0xa0, 0x52, 0xa9, 0x2c, 0x7a, 0x9e, 0x17, 0x1, 0x3d, 0xe0, 0x55, 0x1e, 0x6c, 0x79, 0x9e, 0x17, 0x55, 0x2a, 0x95, 0xc5, 0x1, 0x5, 0xcd, 0x66, 0x33, 0x30, 0xc6, 0x9c, 0xc6, 0x71, 0xbc, 0x2d, 0x22, 0x8f, 0x87, 0x78, 0xb0, 0x6d, 0x8c, 0x39, 0xed, 0xae, 0x74, 0xdf, 0x83, 0x3a, 0x70, 0x9c, 0x65, 0x59, 0x23, 0x49, 0x92, 0x5, 0x11, 0x9, 0x86, 0x79, 0x20, 0x22, 0x41, 0x92, 0x24, 0xb, 0x59, 0x96, 0x35, 0x80, 0x63, 0xa0, 0x2e, 0x3d, 0xf6, 0xc2, 0x91, 0xdc, 0x0, 0x5c, 0x55, 0x5d, 0xbf, 0x4, 0x9e, 0x3, 0x72, 0xfe, 0xaf, 0xfb, 0xaa, 0xe7, 0x79, 0x1f, 0x8d, 0x31, 0x6d, 0x29, 0x36, 0xf5, 0xce, 0x14, 0xb8, 0x33, 0x44, 0xc4, 0x6f, 0xdf, 0xf7, 0xd7, 0x8d, 0x31, 0xed, 0x5e, 0xe2, 0x1f, 0xb, 0x5c, 0xe2, 0xcb, 0xd, 0x9b, 0x69, 0xcb, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; @@ -210,6 +214,14 @@ static const unsigned char icon_zoom_reset_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x0, 0x33, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x20, 0xa, 0x3c, 0xc, 0x7b, 0xf0, 0xff, 0xc1, 0x7f, 0x9c, 0x22, 0xcf, 0x44, 0x1e, 0xbc, 0x84, 0x72, 0xb1, 0x8b, 0x3c, 0x58, 0x5, 0xe4, 0x40, 0xb8, 0x38, 0x45, 0x18, 0x60, 0x5c, 0x84, 0x30, 0x59, 0xa, 0xa0, 0x80, 0x6e, 0xa, 0x86, 0x92, 0x2f, 0x8, 0x3, 0x0, 0x69, 0xc8, 0x86, 0x87, 0x72, 0xca, 0x85, 0x23, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; +static const unsigned char indeterminate_png[] = { + 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x3, 0x0, 0x0, 0x0, 0x28, 0x2d, 0xf, 0x53, 0x0, 0x0, 0x0, 0x36, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x4d, 0x4b, 0x59, 0x4d, 0x4b, 0x59, 0x4d, 0x4b, 0x59, 0x4d, 0x4b, 0x59, 0x4d, 0x4b, 0x59, 0x38, 0x37, 0x40, 0x20, 0x20, 0x24, 0x20, 0x20, 0x24, 0x38, 0x36, 0x40, 0x20, 0x20, 0x25, 0x1e, 0x1e, 0x22, 0x1f, 0x1f, 0x23, 0x20, 0x20, 0x24, 0x22, 0x22, 0x27, 0x23, 0x23, 0x28, 0x25, 0x25, 0x2a, 0xfe, 0xfe, 0xfe, 0x98, 0x4d, 0x2d, 0x9a, 0x0, 0x0, 0x0, 0x12, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x7, 0x27, 0x50, 0x66, 0x68, 0xb4, 0xfa, 0xfb, 0xb4, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1c, 0x77, 0x5e, 0x7f, 0x0, 0x0, 0x0, 0x59, 0x49, 0x44, 0x41, 0x54, 0x18, 0x95, 0x85, 0xcf, 0xc1, 0x12, 0x80, 0x20, 0x8, 0x45, 0xd1, 0x4, 0x14, 0xd, 0xc5, 0xfa, 0xff, 0x9f, 0x8d, 0x9c, 0x6c, 0x9a, 0xb4, 0xe9, 0x2e, 0xcf, 0x42, 0x9e, 0xcb, 0x32, 0xe4, 0x0, 0xc9, 0xb7, 0x8, 0xc1, 0x19, 0x40, 0x60, 0xc9, 0x2d, 0xe1, 0x0, 0x6, 0xc8, 0x45, 0x6b, 0x4b, 0xb, 0xa3, 0x1, 0x89, 0x6e, 0x57, 0x2a, 0x64, 0xe0, 0x73, 0xed, 0x50, 0xb3, 0x9f, 0xc3, 0x7e, 0xf7, 0x5, 0x7f, 0x6f, 0xc, 0x67, 0x9f, 0xc3, 0xe2, 0x39, 0xc, 0x52, 0xec, 0xd3, 0xd7, 0x4, 0xb3, 0xcf, 0xbd, 0x3a, 0x0, 0xa0, 0xa2, 0x8, 0xbc, 0xf6, 0x84, 0x3a, 0x9d, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 +}; + +static const unsigned char indeterminate_disabled_png[] = { + 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x3, 0x0, 0x0, 0x0, 0x28, 0x2d, 0xf, 0x53, 0x0, 0x0, 0x0, 0x3c, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x7a, 0x78, 0x83, 0x7a, 0x78, 0x83, 0x7a, 0x78, 0x83, 0x7a, 0x78, 0x83, 0x7a, 0x78, 0x83, 0x6a, 0x69, 0x70, 0x6a, 0x68, 0x70, 0x58, 0x58, 0x5c, 0x58, 0x58, 0x5b, 0x58, 0x58, 0x5b, 0x5c, 0x5c, 0x5f, 0x5a, 0x5a, 0x5e, 0x59, 0x59, 0x5d, 0x58, 0x58, 0x5b, 0x57, 0x57, 0x5a, 0x56, 0x56, 0x59, 0xff, 0x0, 0x0, 0xff, 0xff, 0xff, 0x9e, 0x9e, 0x9e, 0x8c, 0x93, 0x80, 0x95, 0x0, 0x0, 0x0, 0x14, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x7, 0x27, 0x50, 0x66, 0x68, 0xb4, 0xb4, 0xfa, 0xfa, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x6c, 0xb9, 0x8d, 0x1e, 0x0, 0x0, 0x0, 0x59, 0x49, 0x44, 0x41, 0x54, 0x18, 0x95, 0x85, 0xcf, 0xd1, 0xe, 0x80, 0x20, 0x8, 0x85, 0xe1, 0x4, 0xd4, 0x30, 0x51, 0xb6, 0xde, 0xff, 0x5d, 0x23, 0x97, 0xad, 0xa5, 0xad, 0xff, 0xf2, 0xbb, 0x90, 0xe3, 0xb2, 0xc, 0x39, 0x40, 0xf2, 0x2d, 0x42, 0x70, 0x6, 0x10, 0x58, 0x6b, 0x4b, 0x39, 0x80, 0x1, 0x72, 0x91, 0xdc, 0x92, 0xc2, 0x68, 0x40, 0x2a, 0xdb, 0x95, 0x28, 0x19, 0xf8, 0x9a, 0x3b, 0xe4, 0xea, 0xe7, 0xb0, 0xdf, 0x7d, 0xc1, 0xdf, 0x1b, 0xc3, 0xd9, 0xe7, 0xb0, 0x74, 0xe, 0x83, 0x98, 0xfa, 0xf4, 0x35, 0xc2, 0xec, 0x73, 0xaf, 0xe, 0x57, 0x20, 0x8, 0x2c, 0x1a, 0x56, 0xe5, 0x32, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 +}; + static const unsigned char line_edit_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0xa, 0x4, 0x3, 0x0, 0x0, 0x0, 0x7f, 0x1c, 0xd2, 0x8e, 0x0, 0x0, 0x0, 0x2a, 0x50, 0x4c, 0x54, 0x45, 0x17, 0x16, 0x1a, 0x1d, 0x1c, 0x21, 0x20, 0x1e, 0x24, 0x21, 0x1f, 0x25, 0x1d, 0x1c, 0x21, 0x20, 0x1e, 0x24, 0x1d, 0x1c, 0x21, 0x1d, 0x1c, 0x21, 0x24, 0x22, 0x29, 0x28, 0x26, 0x2d, 0x28, 0x26, 0x2e, 0x2b, 0x2a, 0x31, 0x2c, 0x2a, 0x32, 0xff, 0xff, 0xff, 0xb9, 0x11, 0x56, 0x3e, 0x0, 0x0, 0x0, 0x8, 0x74, 0x52, 0x4e, 0x53, 0x6f, 0xef, 0xf7, 0xf7, 0xf0, 0xf9, 0xf1, 0xee, 0xcf, 0x21, 0xd2, 0xdf, 0x0, 0x0, 0x0, 0x2d, 0x49, 0x44, 0x41, 0x54, 0x8, 0xd7, 0x63, 0x60, 0x54, 0x36, 0x36, 0x12, 0x60, 0xf0, 0x98, 0xb5, 0x6a, 0x65, 0xb, 0x43, 0xe4, 0x9e, 0x33, 0xa7, 0xa7, 0x32, 0x58, 0x9d, 0x39, 0x73, 0x66, 0x31, 0x16, 0x12, 0x22, 0xb, 0x52, 0xd9, 0xc6, 0xc0, 0x2, 0xd4, 0x55, 0x0, 0x0, 0xc, 0x14, 0x1a, 0x90, 0x55, 0x1a, 0xec, 0xdb, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; @@ -283,7 +295,7 @@ static const unsigned char popup_bg_disabled_png[] = { }; static const unsigned char popup_window_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x2a, 0x0, 0x0, 0x0, 0x46, 0x8, 0x3, 0x0, 0x0, 0x0, 0x8d, 0x2b, 0xf6, 0x48, 0x0, 0x0, 0x1, 0x6b, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0xe8, 0xe5, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0x1d, 0x22, 0x0, 0x0, 0x0, 0x1a, 0x19, 0x1c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x1e, 0x23, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0x1d, 0x21, 0x17, 0x16, 0x19, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x21, 0x1f, 0x24, 0x1b, 0x1a, 0x1d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x21, 0x1f, 0x24, 0x1e, 0x1c, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x22, 0x20, 0x25, 0x20, 0x1e, 0x23, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x21, 0x1f, 0x24, 0x0, 0x0, 0x0, 0x21, 0x1f, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x22, 0x20, 0x25, 0x0, 0x0, 0x0, 0x20, 0x20, 0x25, 0x20, 0x1d, 0x25, 0x20, 0x1d, 0x22, 0x1d, 0x1d, 0x22, 0x1d, 0x1d, 0x20, 0x1d, 0x1a, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x32, 0x30, 0x38, 0xe8, 0xe5, 0xf1, 0xe5, 0xe2, 0xeb, 0xe3, 0xe1, 0xe8, 0xe1, 0xdf, 0xe7, 0xe0, 0xde, 0xe6, 0xdf, 0xdd, 0xe5, 0xde, 0xdc, 0xe4, 0xdd, 0xdb, 0xe3, 0xdc, 0xda, 0xe2, 0xda, 0xd8, 0xe0, 0xd9, 0xd7, 0xdf, 0xd7, 0xd6, 0xdf, 0xd6, 0xd4, 0xdd, 0xd5, 0xd3, 0xdc, 0xd4, 0xd1, 0xdb, 0xd3, 0xd0, 0xda, 0xd1, 0xce, 0xd8, 0xd0, 0xcd, 0xd7, 0xcf, 0xcd, 0xd7, 0xe2, 0xdf, 0xeb, 0x48, 0x46, 0x51, 0x42, 0x40, 0x4b, 0x40, 0x3e, 0x48, 0x40, 0x3d, 0x48, 0x48, 0x45, 0x50, 0x42, 0x3f, 0x4a, 0x3f, 0x3d, 0x48, 0x47, 0x44, 0x50, 0x41, 0x3f, 0x4a, 0x3f, 0x3d, 0x47, 0x41, 0x3e, 0x49, 0x3f, 0x3c, 0x47, 0x46, 0x43, 0x4f, 0x3e, 0x3c, 0x46, 0x40, 0x3e, 0x49, 0x3d, 0x3b, 0x46, 0x45, 0x43, 0x4e, 0x3d, 0x3b, 0x45, 0x44, 0x42, 0x4d, 0x3d, 0x3a, 0x45, 0x3e, 0x3c, 0x47, 0x3c, 0x3a, 0x44, 0x43, 0x42, 0x4c, 0x43, 0x40, 0x4c, 0x3e, 0x3b, 0x46, 0x3b, 0x39, 0x43, 0x43, 0x3f, 0x4c, 0x43, 0x3f, 0x4b, 0x3a, 0x38, 0x42, 0x42, 0x3e, 0x4b, 0x42, 0x3e, 0x49, 0x3a, 0x37, 0x41, 0x39, 0x37, 0x41, 0x3f, 0x3e, 0x48, 0x39, 0x37, 0x40, 0x38, 0x36, 0x40, 0x3e, 0x3d, 0x48, 0x38, 0x36, 0x3f, 0x3e, 0x3d, 0x47, 0x3a, 0x38, 0x41, 0x38, 0x35, 0x3f, 0x37, 0x35, 0x3e, 0x39, 0x36, 0x40, 0x37, 0x34, 0x3e, 0x3d, 0x3a, 0x46, 0x36, 0x34, 0x3d, 0x3d, 0x3a, 0x44, 0x37, 0x35, 0x3f, 0x35, 0x33, 0x3c, 0x46, 0x44, 0x4f, 0xac, 0xa5, 0x1, 0x25, 0x0, 0x0, 0x0, 0x33, 0x74, 0x52, 0x4e, 0x53, 0x0, 0xa2, 0x3, 0x9, 0x17, 0xc, 0x20, 0xf, 0x2a, 0x5e, 0x12, 0x30, 0x68, 0x46, 0x20, 0x4e, 0xa2, 0x7d, 0x3a, 0x4f, 0xa4, 0x7d, 0x3f, 0x25, 0x60, 0xc0, 0xb8, 0x57, 0x1d, 0xba, 0x59, 0xbd, 0x5b, 0x22, 0xbf, 0x5e, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xa1, 0x9f, 0x9e, 0x52, 0x92, 0x15, 0x44, 0x7e, 0xd8, 0x5, 0xc7, 0xf4, 0xac, 0x0, 0x0, 0x1, 0x98, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xed, 0xd6, 0x55, 0x9e, 0x14, 0x31, 0x10, 0x80, 0xf1, 0xb2, 0x20, 0x1d, 0xdc, 0x9d, 0x3b, 0x2c, 0xa7, 0x87, 0x4b, 0xe0, 0xee, 0xd0, 0x82, 0xcb, 0xea, 0xb4, 0x86, 0x79, 0x23, 0x93, 0xaa, 0xcc, 0xf, 0xd7, 0xfd, 0x9e, 0xff, 0xed, 0x1d, 0x21, 0xf8, 0xe2, 0xfe, 0x1c, 0x8a, 0x17, 0x32, 0xa1, 0xa2, 0x2b, 0x48, 0x66, 0xb8, 0xa2, 0x28, 0x10, 0x9a, 0xd1, 0xf7, 0x3d, 0x16, 0x66, 0xfa, 0x45, 0x74, 0x9b, 0xd2, 0x97, 0x52, 0xe2, 0x2f, 0xa6, 0x40, 0x5f, 0x4c, 0x1d, 0xf3, 0x97, 0x52, 0x5e, 0x46, 0xf7, 0xee, 0xe3, 0x88, 0x8a, 0x48, 0x9e, 0x8a, 0xec, 0x8c, 0x28, 0x2c, 0xa7, 0xf0, 0x99, 0xd2, 0x6e, 0xe7, 0x8e, 0x10, 0x9b, 0xd1, 0x11, 0xe7, 0xe, 0xd1, 0x17, 0x8e, 0x2, 0xe6, 0x55, 0xf8, 0x42, 0x4a, 0x74, 0x18, 0xbe, 0xf8, 0xac, 0x9b, 0x5f, 0x4a, 0xb7, 0x36, 0x20, 0xa5, 0xf9, 0x2f, 0x90, 0x50, 0x11, 0x36, 0x13, 0x49, 0xa9, 0xe7, 0x6c, 0x5e, 0xcf, 0x2e, 0x99, 0xf4, 0xd, 0xb8, 0x8c, 0x5, 0xa7, 0x28, 0x8, 0x98, 0x9, 0x68, 0xba, 0x41, 0x66, 0x1b, 0x8a, 0xa2, 0xb0, 0x4d, 0x59, 0x30, 0xa5, 0x94, 0xa3, 0x94, 0x52, 0x0, 0x6f, 0x53, 0x6f, 0x7c, 0x82, 0x16, 0xcc, 0x5a, 0x51, 0x14, 0xbd, 0x98, 0x79, 0x4c, 0x29, 0x72, 0xee, 0xac, 0x8c, 0xea, 0xac, 0xb9, 0x51, 0xa0, 0xce, 0xa, 0x44, 0x60, 0x46, 0xa4, 0x28, 0x2, 0x9b, 0x1, 0x2a, 0x5a, 0xa, 0x9a, 0x49, 0xa9, 0xe8, 0x79, 0x20, 0x33, 0x38, 0xaf, 0x68, 0xc5, 0x68, 0xc6, 0x95, 0xa2, 0xe7, 0x72, 0x67, 0x3d, 0x97, 0x52, 0x24, 0xcf, 0x66, 0x9e, 0x30, 0xa5, 0xef, 0x5a, 0x34, 0x6b, 0xdf, 0xa5, 0x14, 0xa4, 0x0, 0xb3, 0x42, 0x8c, 0xe5, 0x38, 0x37, 0xb4, 0x14, 0x3d, 0xd2, 0xda, 0xb4, 0x3d, 0xa2, 0x68, 0xe3, 0xc0, 0xcc, 0x35, 0x8a, 0x9e, 0x86, 0x4c, 0xa7, 0x15, 0x85, 0x9d, 0x6c, 0xb6, 0x13, 0x16, 0xe8, 0x54, 0x78, 0xbc, 0x8e, 0x60, 0x86, 0xd7, 0xd1, 0x17, 0xd3, 0x37, 0x6e, 0x48, 0x30, 0x4f, 0x70, 0x81, 0xbe, 0xed, 0x97, 0xd1, 0xfe, 0x6d, 0x44, 0xf7, 0xed, 0xa7, 0x63, 0x39, 0x79, 0x8c, 0xf6, 0xef, 0x8b, 0xe8, 0x61, 0xe6, 0x8d, 0x55, 0x5b, 0xae, 0x9e, 0x62, 0x3e, 0x1c, 0xd1, 0x3b, 0xf7, 0x9b, 0xc7, 0xfb, 0x9b, 0xab, 0x46, 0xcd, 0xab, 0x4b, 0xcd, 0xfd, 0x3b, 0x11, 0x1d, 0xe, 0x76, 0xf5, 0xc5, 0x2b, 0xe5, 0xf3, 0x67, 0x49, 0xcf, 0xcb, 0x2b, 0x8f, 0xea, 0xee, 0xe0, 0x10, 0xd1, 0xf0, 0xb2, 0x58, 0xb, 0x61, 0x75, 0xd6, 0x26, 0xcd, 0x56, 0x43, 0x58, 0x2b, 0x5e, 0x86, 0x88, 0x4e, 0x63, 0x33, 0x84, 0xf7, 0x1f, 0x26, 0xd5, 0x87, 0xf7, 0x61, 0x68, 0xc6, 0x29, 0xa2, 0x73, 0xdb, 0x5e, 0x2d, 0x57, 0x1f, 0xab, 0x56, 0xcb, 0xab, 0xed, 0x5c, 0xfe, 0xc4, 0x5d, 0xf1, 0x27, 0x1a, 0x8f, 0xba, 0x8d, 0xd7, 0xa0, 0x9a, 0x40, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 + 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x2a, 0x0, 0x0, 0x0, 0x48, 0x8, 0x3, 0x0, 0x0, 0x0, 0xb7, 0x21, 0x97, 0x38, 0x0, 0x0, 0x1, 0x71, 0x69, 0x43, 0x43, 0x50, 0x69, 0x63, 0x63, 0x0, 0x0, 0x28, 0x91, 0x75, 0x91, 0x3d, 0x4b, 0xc3, 0x50, 0x14, 0x86, 0xdf, 0xb6, 0x96, 0x8a, 0xad, 0x74, 0xd0, 0x41, 0xa4, 0x43, 0x86, 0x2a, 0xe, 0x2d, 0x14, 0x5, 0x71, 0xd4, 0x3a, 0x74, 0x29, 0x52, 0x6a, 0x5, 0xab, 0x2e, 0xc9, 0x6d, 0xd2, 0xa, 0x49, 0x1a, 0x6e, 0x52, 0xa4, 0xb8, 0xa, 0x2e, 0xe, 0x5, 0x7, 0xd1, 0xc5, 0xaf, 0xc1, 0x7f, 0xa0, 0xab, 0xe0, 0xaa, 0x20, 0x8, 0x8a, 0x20, 0xe2, 0xe2, 0x1f, 0xf0, 0x6b, 0x91, 0x12, 0xcf, 0x6d, 0xa, 0x2d, 0xd2, 0x9e, 0x70, 0x73, 0x1e, 0xde, 0x7b, 0xde, 0xc3, 0xbd, 0xe7, 0x2, 0xfe, 0xac, 0xce, 0xc, 0x7b, 0x20, 0x5, 0x18, 0xa6, 0xc3, 0xf3, 0x99, 0xb4, 0xb4, 0x5a, 0x5c, 0x93, 0x42, 0xef, 0x8, 0x21, 0x6, 0x1f, 0x82, 0x8, 0xcb, 0xcc, 0xb6, 0x16, 0x72, 0xb9, 0x2c, 0xfa, 0xc6, 0xcf, 0x23, 0x55, 0x52, 0x3c, 0x24, 0x45, 0xaf, 0xfe, 0x75, 0x3d, 0x23, 0x5c, 0x52, 0x6d, 0x6, 0xf8, 0x6, 0x89, 0x67, 0x99, 0xc5, 0x1d, 0xe2, 0x79, 0xe2, 0xec, 0x96, 0x63, 0x9, 0xde, 0x23, 0x1e, 0x65, 0x15, 0xb9, 0x44, 0x7c, 0x42, 0x9c, 0xe0, 0x74, 0x40, 0xe2, 0x5b, 0xa1, 0x2b, 0x1e, 0xbf, 0x9, 0x2e, 0x7b, 0xfc, 0x25, 0x98, 0x17, 0xf2, 0x8b, 0x80, 0x5f, 0xf4, 0x94, 0xca, 0x5d, 0xac, 0x74, 0x31, 0xab, 0x70, 0x83, 0x78, 0x8a, 0x38, 0x6e, 0xe8, 0x35, 0xd6, 0x3e, 0x8f, 0xb8, 0x49, 0x44, 0x35, 0x57, 0x96, 0x29, 0x8f, 0xd3, 0x8a, 0xc1, 0x46, 0x1e, 0x19, 0xa4, 0x21, 0x41, 0x41, 0xd, 0x9b, 0xd0, 0xe1, 0x20, 0x49, 0xd9, 0xa4, 0x99, 0xf5, 0xf6, 0xa5, 0x5a, 0xbe, 0x25, 0x54, 0xc9, 0xc3, 0xe8, 0x6f, 0xa1, 0xe, 0x4e, 0x8e, 0x32, 0x2a, 0xe4, 0x4d, 0x90, 0x5a, 0xa3, 0xae, 0x2a, 0x65, 0x8d, 0x74, 0x95, 0x3e, 0x1d, 0x75, 0x31, 0xf7, 0xff, 0xf3, 0xb4, 0xb5, 0x99, 0x69, 0xaf, 0x7b, 0x24, 0xd, 0x4, 0x5f, 0x5d, 0xf7, 0x73, 0x2, 0x8, 0xed, 0x3, 0xcd, 0x86, 0xeb, 0xfe, 0x9e, 0xba, 0x6e, 0xf3, 0xc, 0x8, 0xbc, 0x0, 0xd7, 0x66, 0xc7, 0x5f, 0xa5, 0x39, 0xcd, 0x7d, 0x93, 0xde, 0xe8, 0x68, 0xf1, 0x63, 0x20, 0xba, 0x3, 0x5c, 0xde, 0x74, 0x34, 0xe5, 0x0, 0xb8, 0xda, 0x5, 0xc6, 0x9e, 0x2d, 0x99, 0xcb, 0x2d, 0x29, 0x40, 0xcb, 0xaf, 0x69, 0xc0, 0xc7, 0x5, 0x30, 0x5c, 0x4, 0x46, 0xee, 0x81, 0xa1, 0x75, 0x6f, 0x56, 0xed, 0x7d, 0x9c, 0x3f, 0x1, 0x85, 0x6d, 0x7a, 0xa2, 0x3b, 0xe0, 0xf0, 0x8, 0x98, 0xa4, 0xfa, 0xe8, 0xc6, 0x1f, 0x7b, 0xde, 0x67, 0xcd, 0xda, 0x7, 0x3a, 0xfb, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xb, 0x12, 0x0, 0x0, 0xb, 0x12, 0x1, 0xd2, 0xdd, 0x7e, 0xfc, 0x0, 0x0, 0x0, 0xa8, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe8, 0xe5, 0xf1, 0x20, 0x1e, 0x23, 0x21, 0x1f, 0x24, 0x21, 0x1f, 0x24, 0x22, 0x20, 0x25, 0x1d, 0x1a, 0x20, 0x1d, 0x1d, 0x20, 0x1d, 0x1d, 0x22, 0x20, 0x1d, 0x22, 0x20, 0x1d, 0x25, 0x20, 0x20, 0x25, 0x22, 0x20, 0x25, 0xcf, 0xcd, 0xd7, 0xd0, 0xcd, 0xd7, 0xd1, 0xce, 0xd8, 0xd3, 0xd0, 0xda, 0xd4, 0xd1, 0xdb, 0xd5, 0xd3, 0xdc, 0xd6, 0xd4, 0xdd, 0xd7, 0xd6, 0xdf, 0xd9, 0xd7, 0xdf, 0xda, 0xd8, 0xe0, 0xdc, 0xda, 0xe2, 0xdd, 0xdb, 0xe3, 0xde, 0xdc, 0xe4, 0xdf, 0xdd, 0xe5, 0xe0, 0xde, 0xe6, 0xe1, 0xdf, 0xe7, 0xe2, 0xdf, 0xeb, 0xe3, 0xe1, 0xe8, 0xe5, 0xe2, 0xeb, 0xe8, 0xe5, 0xf1, 0xe8, 0x4e, 0x48, 0xd9, 0x0, 0x0, 0x0, 0x24, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x3, 0x9, 0xc, 0xf, 0x15, 0x1d, 0x20, 0x22, 0x25, 0x30, 0x3a, 0x44, 0x52, 0x57, 0x59, 0x5b, 0x5e, 0x60, 0x7e, 0x92, 0x9e, 0x9f, 0xa1, 0xa2, 0xb8, 0xba, 0xbd, 0xbf, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xf5, 0xb3, 0x97, 0xfb, 0x0, 0x0, 0x0, 0xea, 0x49, 0x44, 0x41, 0x54, 0x48, 0xc7, 0xed, 0x96, 0x31, 0xe, 0x83, 0x30, 0xc, 0x45, 0x6d, 0x93, 0xc2, 0xda, 0xb5, 0x13, 0xf7, 0x3f, 0x4a, 0xef, 0xd0, 0xa5, 0xc7, 0x28, 0x10, 0xff, 0xe, 0x4, 0x35, 0x88, 0xc6, 0x25, 0x48, 0x34, 0x2a, 0x6a, 0x16, 0x86, 0x3c, 0x7d, 0xbe, 0xfd, 0x49, 0x30, 0x51, 0xe1, 0xc5, 0xe1, 0xc1, 0xc2, 0xcc, 0xbc, 0xd8, 0x6, 0x0, 0x5, 0x22, 0x94, 0xa5, 0x72, 0xc2, 0x22, 0xb, 0x54, 0x15, 0x3a, 0x78, 0x5, 0x11, 0x91, 0x1b, 0xd1, 0xea, 0xd4, 0xf0, 0x1b, 0x59, 0x0, 0xc0, 0x83, 0x10, 0xa1, 0xe2, 0x9a, 0xba, 0x4d, 0x58, 0xbc, 0x13, 0x54, 0x23, 0x55, 0xe1, 0x36, 0x38, 0x5a, 0x14, 0xd3, 0xde, 0x64, 0x7c, 0x59, 0x40, 0x99, 0xc9, 0x27, 0x54, 0xdd, 0xe4, 0x4b, 0x26, 0x55, 0xa3, 0x49, 0x73, 0x55, 0x61, 0xc2, 0x3a, 0x94, 0xc8, 0x42, 0x27, 0x27, 0xaf, 0xc6, 0x7c, 0x4a, 0xcb, 0x85, 0xaa, 0xea, 0x34, 0x5a, 0xf7, 0xcc, 0xd8, 0xa0, 0x7a, 0x5c, 0x34, 0x23, 0x82, 0x1d, 0xd2, 0xda, 0x11, 0x3d, 0xaf, 0x45, 0xdd, 0x35, 0x89, 0x5c, 0x6, 0x1f, 0x9d, 0x2, 0xfb, 0xaa, 0x98, 0x1d, 0x98, 0x2, 0x68, 0x13, 0x32, 0x90, 0xf5, 0xd7, 0xdb, 0x6, 0xd4, 0x8, 0x0, 0xf4, 0xd, 0x3, 0xc5, 0xcb, 0xfa, 0xa5, 0xe, 0x1c, 0x34, 0xad, 0xff, 0xe7, 0x72, 0x44, 0x14, 0x40, 0x97, 0x46, 0x3a, 0x8c, 0x63, 0xcd, 0x9e, 0x69, 0x41, 0x2d, 0x34, 0x6c, 0xe6, 0xa3, 0xb0, 0x50, 0x64, 0xab, 0x86, 0x3f, 0x82, 0xb3, 0xc6, 0xc7, 0x7e, 0x88, 0x66, 0x42, 0x78, 0x82, 0x31, 0x94, 0x22, 0x77, 0xd4, 0x2d, 0xbe, 0x9e, 0x25, 0x9d, 0x7b, 0xb0, 0x59, 0x6d, 0x9f, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; static const unsigned char progress_bar_png[] = { diff --git a/scene/resources/environment.cpp b/scene/resources/environment.cpp index 8550af8bcb..cab6c0378a 100644 --- a/scene/resources/environment.cpp +++ b/scene/resources/environment.cpp @@ -449,6 +449,7 @@ bool Environment::is_sdfgi_enabled() const { } void Environment::set_sdfgi_cascades(SDFGICascades p_cascades) { + ERR_FAIL_INDEX(p_cascades, SDFGI_CASCADES_8 + 1); sdfgi_cascades = p_cascades; _update_sdfgi(); } diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp index 032171847d..7af8e900fd 100644 --- a/scene/resources/font.cpp +++ b/scene/resources/font.cpp @@ -36,54 +36,129 @@ #include "scene/resources/text_line.h" #include "scene/resources/text_paragraph.h" -void FontData::_bind_methods() { - ClassDB::bind_method(D_METHOD("load_resource", "filename", "base_size"), &FontData::load_resource, DEFVAL(16)); - ClassDB::bind_method(D_METHOD("load_memory", "data", "type", "base_size"), &FontData::_load_memory, DEFVAL(16)); - ClassDB::bind_method(D_METHOD("new_bitmap", "height", "ascent", "base_size"), &FontData::new_bitmap); - - ClassDB::bind_method(D_METHOD("bitmap_add_texture", "texture"), &FontData::bitmap_add_texture); - ClassDB::bind_method(D_METHOD("bitmap_add_char", "char", "texture_idx", "rect", "align", "advance"), &FontData::bitmap_add_char); - ClassDB::bind_method(D_METHOD("bitmap_add_kerning_pair", "A", "B", "kerning"), &FontData::bitmap_add_kerning_pair); +_FORCE_INLINE_ void FontData::_clear_cache() { + for (int i = 0; i < cache.size(); i++) { + if (cache[i].is_valid()) { + TS->free(cache[i]); + cache.write[i] = RID(); + } + } +} - ClassDB::bind_method(D_METHOD("set_data_path", "path"), &FontData::set_data_path); - ClassDB::bind_method(D_METHOD("get_data_path"), &FontData::get_data_path); +_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_oversampling(cache[p_cache_index], oversampling); + } +} - ClassDB::bind_method(D_METHOD("get_height", "size"), &FontData::get_height); - ClassDB::bind_method(D_METHOD("get_ascent", "size"), &FontData::get_ascent); - ClassDB::bind_method(D_METHOD("get_descent", "size"), &FontData::get_descent); +void FontData::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_data", "data"), &FontData::set_data); + ClassDB::bind_method(D_METHOD("get_data"), &FontData::get_data); - ClassDB::bind_method(D_METHOD("get_underline_position", "size"), &FontData::get_underline_position); - ClassDB::bind_method(D_METHOD("get_underline_thickness", "size"), &FontData::get_underline_thickness); + ClassDB::bind_method(D_METHOD("set_antialiased", "antialiased"), &FontData::set_antialiased); + ClassDB::bind_method(D_METHOD("is_antialiased"), &FontData::is_antialiased); - ClassDB::bind_method(D_METHOD("get_spacing", "type"), &FontData::get_spacing); - ClassDB::bind_method(D_METHOD("set_spacing", "type", "value"), &FontData::set_spacing); + 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); - ClassDB::bind_method(D_METHOD("set_antialiased", "antialiased"), &FontData::set_antialiased); - ClassDB::bind_method(D_METHOD("get_antialiased"), &FontData::get_antialiased); + 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); - ClassDB::bind_method(D_METHOD("get_variation_list"), &FontData::get_variation_list); + 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); - ClassDB::bind_method(D_METHOD("set_variation", "tag", "value"), &FontData::set_variation); - ClassDB::bind_method(D_METHOD("get_variation", "tag"), &FontData::get_variation); + 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); ClassDB::bind_method(D_METHOD("set_hinting", "hinting"), &FontData::set_hinting); ClassDB::bind_method(D_METHOD("get_hinting"), &FontData::get_hinting); - ClassDB::bind_method(D_METHOD("set_force_autohinter", "enabled"), &FontData::set_force_autohinter); - ClassDB::bind_method(D_METHOD("get_force_autohinter"), &FontData::get_force_autohinter); + ClassDB::bind_method(D_METHOD("set_oversampling", "oversampling"), &FontData::set_oversampling); + ClassDB::bind_method(D_METHOD("get_oversampling"), &FontData::get_oversampling); - ClassDB::bind_method(D_METHOD("set_distance_field_hint", "distance_field"), &FontData::set_distance_field_hint); - ClassDB::bind_method(D_METHOD("get_distance_field_hint"), &FontData::get_distance_field_hint); + ClassDB::bind_method(D_METHOD("find_cache", "variation_coordinates"), &FontData::find_cache); - ClassDB::bind_method(D_METHOD("has_char", "char"), &FontData::has_char); - ClassDB::bind_method(D_METHOD("get_supported_chars"), &FontData::get_supported_chars); + 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); + + 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); + + 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); + + 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); + + 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); + + 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); + + 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); + + 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); + + ClassDB::bind_method(D_METHOD("set_spacing", "cache_index", "size", "spacing"), &FontData::set_spacing); + ClassDB::bind_method(D_METHOD("get_spacing", "cache_index", "size"), &FontData::get_spacing); + + 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); + + 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); + + 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); + + 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); + + 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); + + 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); + + 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); - ClassDB::bind_method(D_METHOD("get_glyph_advance", "index", "size"), &FontData::get_glyph_advance); - ClassDB::bind_method(D_METHOD("get_glyph_kerning", "index_a", "index_b", "size"), &FontData::get_glyph_kerning); + 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); - ClassDB::bind_method(D_METHOD("get_base_size"), &FontData::get_base_size); + 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); - ClassDB::bind_method(D_METHOD("has_outline"), &FontData::has_outline); + 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); + + 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); + + 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); + + ClassDB::bind_method(D_METHOD("get_cache_rid", "cache_index"), &FontData::get_cache_rid); 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); @@ -97,511 +172,915 @@ void FontData::_bind_methods() { 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); - ClassDB::bind_method(D_METHOD("get_glyph_index", "char", "variation_selector"), &FontData::get_glyph_index, DEFVAL(0x0000)); - ClassDB::bind_method(D_METHOD("draw_glyph", "canvas", "size", "pos", "index", "color"), &FontData::draw_glyph, DEFVAL(Color(1, 1, 1))); - ClassDB::bind_method(D_METHOD("draw_glyph_outline", "canvas", "size", "outline_size", "pos", "index", "color"), &FontData::draw_glyph_outline, DEFVAL(Color(1, 1, 1))); - - ADD_PROPERTY(PropertyInfo(Variant::STRING, "data_path", PROPERTY_HINT_FILE, "*.ttf,*.otf,*.woff,*.fnt,*.font"), "set_data_path", "get_data_path"); - - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "antialiased"), "set_antialiased", "get_antialiased"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "force_autohinter"), "set_force_autohinter", "get_force_autohinter"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "distance_field_hint"), "set_distance_field_hint", "get_distance_field_hint"); - - ADD_PROPERTY(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal"), "set_hinting", "get_hinting"); + ClassDB::bind_method(D_METHOD("has_char", "char"), &FontData::has_char); + ClassDB::bind_method(D_METHOD("get_supported_chars"), &FontData::get_supported_chars); - ADD_GROUP("Extra Spacing", "extra_spacing"); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "extra_spacing_glyph"), "set_spacing", "get_spacing", SPACING_GLYPH); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "extra_spacing_space"), "set_spacing", "get_spacing", SPACING_SPACE); + ClassDB::bind_method(D_METHOD("get_glyph_index", "char", "variation_selector"), &FontData::get_glyph_index); - BIND_ENUM_CONSTANT(SPACING_GLYPH); - BIND_ENUM_CONSTANT(SPACING_SPACE); + 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); } bool FontData::_set(const StringName &p_name, const Variant &p_value) { - String str = p_name; - if (str.begins_with("language_support_override/")) { - String lang = str.get_slicec('/', 1); - if (lang == "_new") { - return false; + Vector<String> tokens = p_name.operator String().split("/"); + if (tokens.size() == 1) { + if (tokens[0] == "data") { + set_data(p_value); + return true; + } else if (tokens[0] == "antialiased") { + set_antialiased(p_value); + return true; + } else if (tokens[0] == "multichannel_signed_distance_field") { + set_multichannel_signed_distance_field(p_value); + return true; + } else if (tokens[0] == "msdf_pixel_range") { + set_msdf_pixel_range(p_value); + return true; + } else if (tokens[0] == "msdf_size") { + set_msdf_size(p_value); + return true; + } else if (tokens[0] == "fixed_size") { + set_fixed_size(p_value); + return true; + } else if (tokens[0] == "hinting") { + set_hinting((TextServer::Hinting)p_value.operator int()); + return true; + } else if (tokens[0] == "force_autohinter") { + set_force_autohinter(p_value); + return true; + } else if (tokens[0] == "oversampling") { + set_oversampling(p_value); + return true; } + } else if (tokens.size() == 2 && tokens[0] == "language_support_override") { + String lang = tokens[1]; set_language_support_override(lang, p_value); return true; - } - if (str.begins_with("script_support_override/")) { - String scr = str.get_slicec('/', 1); - if (scr == "_new") { - return false; - } - set_script_support_override(scr, p_value); - return true; - } - if (str.begins_with("variation/")) { - String name = str.get_slicec('/', 1); - set_variation(name, p_value); + } 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; + } + 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 false; } bool FontData::_get(const StringName &p_name, Variant &r_ret) const { - String str = p_name; - if (str.begins_with("language_support_override/")) { - String lang = str.get_slicec('/', 1); - if (lang == "_new") { + Vector<String> tokens = p_name.operator String().split("/"); + if (tokens.size() == 1) { + if (tokens[0] == "data") { + r_ret = get_data(); + return true; + } else if (tokens[0] == "antialiased") { + r_ret = is_antialiased(); + return true; + } else if (tokens[0] == "multichannel_signed_distance_field") { + r_ret = is_multichannel_signed_distance_field(); + return true; + } else if (tokens[0] == "msdf_pixel_range") { + r_ret = get_msdf_pixel_range(); + return true; + } else if (tokens[0] == "msdf_size") { + r_ret = get_msdf_size(); + return true; + } else if (tokens[0] == "fixed_size") { + r_ret = get_fixed_size(); + return true; + } else if (tokens[0] == "hinting") { + r_ret = get_hinting(); + return true; + } else if (tokens[0] == "force_autohinter") { + r_ret = is_force_autohinter(); + return true; + } else if (tokens[0] == "oversampling") { + r_ret = get_oversampling(); return true; } + } else if (tokens.size() == 2 && tokens[0] == "language_support_override") { + String lang = tokens[1]; r_ret = get_language_support_override(lang); return true; - } - if (str.begins_with("script_support_override/")) { - String scr = str.get_slicec('/', 1); - if (scr == "_new") { + } 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; } - r_ret = get_script_support_override(scr); - return true; - } - if (str.begins_with("variation/")) { - String name = str.get_slicec('/', 1); - - r_ret = get_variation(name); - 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 { + p_list->push_back(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + + p_list->push_back(PropertyInfo(Variant::BOOL, "antialiased", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::BOOL, "multichannel_signed_distance_field", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::INT, "msdf_pixel_range", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::INT, "msdf_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::INT, "fixed_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::BOOL, "force_autohinter", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::FLOAT, "oversampling", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); + 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_EDITOR | PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::BOOL, "language_support_override/" + lang_over[i], PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); } - p_list->push_back(PropertyInfo(Variant::NIL, "language_support_override/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); - 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_EDITOR | PROPERTY_USAGE_STORAGE)); - } - p_list->push_back(PropertyInfo(Variant::NIL, "script_support_override/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); + 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)); + 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)); + } - Dictionary variations = get_variation_list(); - for (const Variant *ftr = variations.next(nullptr); ftr != nullptr; ftr = variations.next(ftr)) { - Vector3i v = variations[*ftr]; - p_list->push_back(PropertyInfo(Variant::FLOAT, "variation/" + TS->tag_to_name(*ftr), PROPERTY_HINT_RANGE, itos(v.x) + "," + itos(v.y), PROPERTY_USAGE_EDITOR | 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 FontData::reset_state() { - if (rid != RID()) { - TS->free(rid); - } - base_size = 16; - path = String(); -} + _clear_cache(); + data.clear(); + data_ptr = nullptr; + data_size = 0; + cache.clear(); -RID FontData::get_rid() const { - return rid; + antialiased = true; + msdf = false; + force_autohinter = false; + hinting = TextServer::HINTING_LIGHT; + msdf_pixel_range = 14; + msdf_size = 128; + oversampling = 0.f; } -void FontData::load_resource(const String &p_filename, int p_base_size) { - if (rid != RID()) { - TS->free(rid); +/*************************************************************************/ + +void FontData::set_data_ptr(const uint8_t *p_data, size_t p_size) { + data.clear(); + data_ptr = p_data; + data_size = p_size; + + if (data_ptr != nullptr) { + for (int i = 0; i < cache.size(); i++) { + if (cache[i].is_valid()) { + TS->font_set_data_ptr(cache[i], data_ptr, data_size); + } + } } - rid = TS->create_font_resource(p_filename, p_base_size); - path = p_filename; - base_size = TS->font_get_base_size(rid); - emit_changed(); } -void FontData::_load_memory(const PackedByteArray &p_data, const String &p_type, int p_base_size) { - if (rid != RID()) { - TS->free(rid); +void FontData::set_data(const PackedByteArray &p_data) { + data = p_data; + data_ptr = data.ptr(); + data_size = data.size(); + + if (data_ptr != nullptr) { + for (int i = 0; i < cache.size(); i++) { + if (cache[i].is_valid()) { + TS->font_set_data_ptr(cache[i], data_ptr, data_size); + } + } } - rid = TS->create_font_memory(p_data.ptr(), p_data.size(), p_type, p_base_size); - path = TTR("(Memory: " + p_type.to_upper() + " @ 0x" + String::num_int64((uint64_t)p_data.ptr(), 16, true) + ")"); - base_size = TS->font_get_base_size(rid); - emit_changed(); } -void FontData::load_memory(const uint8_t *p_data, size_t p_size, const String &p_type, int p_base_size) { - if (rid != RID()) { - TS->free(rid); - } - rid = TS->create_font_memory(p_data, p_size, p_type, p_base_size); - path = TTR("(Memory: " + p_type.to_upper() + " @ 0x" + String::num_int64((uint64_t)p_data, 16, true) + ")"); - base_size = TS->font_get_base_size(rid); - emit_changed(); +PackedByteArray FontData::get_data() const { + return data; } -void FontData::new_bitmap(float p_height, float p_ascent, int p_base_size) { - if (rid != RID()) { - TS->free(rid); +void FontData::set_antialiased(bool p_antialiased) { + if (antialiased != p_antialiased) { + antialiased = p_antialiased; + for (int i = 0; i < cache.size(); i++) { + _ensure_rid(i); + TS->font_set_antialiased(cache[i], antialiased); + } + emit_changed(); } - rid = TS->create_font_bitmap(p_height, p_ascent, p_base_size); - path = TTR("(Bitmap: " + String::num_int64(rid.get_id(), 16, true) + ")"); - base_size = TS->font_get_base_size(rid); - emit_changed(); } -void FontData::bitmap_add_texture(const Ref<Texture> &p_texture) { - if (rid != RID()) { - TS->font_bitmap_add_texture(rid, p_texture); - } +bool FontData::is_antialiased() const { + return antialiased; } -void FontData::bitmap_add_char(char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) { - if (rid != RID()) { - TS->font_bitmap_add_char(rid, p_char, p_texture_idx, p_rect, p_align, p_advance); +void FontData::set_multichannel_signed_distance_field(bool p_msdf) { + if (msdf != p_msdf) { + msdf = p_msdf; + for (int i = 0; i < cache.size(); i++) { + _ensure_rid(i); + TS->font_set_multichannel_signed_distance_field(cache[i], msdf); + } + emit_changed(); } } -void FontData::bitmap_add_kerning_pair(char32_t p_A, char32_t p_B, int p_kerning) { - if (rid != RID()) { - TS->font_bitmap_add_kerning_pair(rid, p_A, p_B, p_kerning); - } +bool FontData::is_multichannel_signed_distance_field() const { + return msdf; } -void FontData::set_data_path(const String &p_path) { - load_resource(p_path, base_size); +void FontData::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++) { + _ensure_rid(i); + TS->font_set_msdf_pixel_range(cache[i], msdf_pixel_range); + } + emit_changed(); + } } -String FontData::get_data_path() const { - return path; +int FontData::get_msdf_pixel_range() const { + return msdf_pixel_range; } -float FontData::get_height(int p_size) const { - if (rid == RID()) { - return 0.f; // Do not raise errors in getters, to prevent editor from spamming errors on incomplete (without data_path set) fonts. +void FontData::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++) { + _ensure_rid(i); + TS->font_set_msdf_size(cache[i], msdf_size); + } + emit_changed(); } - return TS->font_get_height(rid, (p_size < 0) ? base_size : p_size); } -float FontData::get_ascent(int p_size) const { - if (rid == RID()) { - return 0.f; - } - return TS->font_get_ascent(rid, (p_size < 0) ? base_size : p_size); +int FontData::get_msdf_size() const { + return msdf_size; } -float FontData::get_descent(int p_size) const { - if (rid == RID()) { - return 0.f; +void FontData::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++) { + _ensure_rid(i); + TS->font_set_fixed_size(cache[i], fixed_size); + } + emit_changed(); } - return TS->font_get_descent(rid, (p_size < 0) ? base_size : p_size); } -float FontData::get_underline_position(int p_size) const { - if (rid == RID()) { - return 0.f; - } - return TS->font_get_underline_position(rid, (p_size < 0) ? base_size : p_size); +int FontData::get_fixed_size() const { + return fixed_size; } -Dictionary FontData::get_feature_list() const { - if (rid == RID()) { - return Dictionary(); +void FontData::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++) { + _ensure_rid(i); + TS->font_set_force_autohinter(cache[i], force_autohinter); + } + emit_changed(); } - return TS->font_get_feature_list(rid); } -float FontData::get_underline_thickness(int p_size) const { - if (rid == RID()) { - return 0.f; - } - return TS->font_get_underline_thickness(rid, (p_size < 0) ? base_size : p_size); +bool FontData::is_force_autohinter() const { + return force_autohinter; } -Dictionary FontData::get_variation_list() const { - if (rid == RID()) { - return Dictionary(); +void FontData::set_hinting(TextServer::Hinting p_hinting) { + if (hinting != p_hinting) { + hinting = p_hinting; + for (int i = 0; i < cache.size(); i++) { + _ensure_rid(i); + TS->font_set_hinting(cache[i], hinting); + } + emit_changed(); } - return TS->font_get_variation_list(rid); } -void FontData::set_variation(const String &p_name, double p_value) { - ERR_FAIL_COND(rid == RID()); - TS->font_set_variation(rid, p_name, p_value); - emit_changed(); +TextServer::Hinting FontData::get_hinting() const { + return hinting; } -double FontData::get_variation(const String &p_name) const { - if (rid == RID()) { - return 0; +void FontData::set_oversampling(real_t p_oversampling) { + if (oversampling != p_oversampling) { + oversampling = p_oversampling; + for (int i = 0; i < cache.size(); i++) { + _ensure_rid(i); + TS->font_set_oversampling(cache[i], oversampling); + } + emit_changed(); + } +} + +real_t FontData::get_oversampling() const { + return oversampling; +} + +RID FontData::find_cache(const Dictionary &p_variation_coordinates) 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; + for (const Variant *V = supported_coords.next(nullptr); V && match; V = supported_coords.next(V)) { + const Vector3 &def = supported_coords[*V]; + + real_t c_v = def.z; + if (cache_var.has(*V)) { + real_t val = cache_var[*V]; + c_v = CLAMP(val, def.x, def.y); + } + if (cache_var.has(TS->tag_to_name(*V))) { + real_t val = cache_var[TS->tag_to_name(*V)]; + c_v = CLAMP(val, def.x, def.y); + } + + real_t s_v = def.z; + if (p_variation_coordinates.has(*V)) { + real_t val = p_variation_coordinates[*V]; + s_v = CLAMP(val, def.x, def.y); + } + if (p_variation_coordinates.has(TS->tag_to_name(*V))) { + real_t val = p_variation_coordinates[TS->tag_to_name(*V)]; + s_v = CLAMP(val, def.x, def.y); + } + + match = match && (c_v == s_v); + } + if (match) { + return cache[i]; + } + } } - return TS->font_get_variation(rid, p_name); + + // Create new variation cache. + int idx = cache.size(); + _ensure_rid(idx); + TS->font_set_variation_coordinates(cache[idx], p_variation_coordinates); + return cache[idx]; } -int FontData::get_spacing(int p_type) const { - if (rid == RID()) { - return 0; - } - if (p_type == SPACING_GLYPH) { - return TS->font_get_spacing_glyph(rid); - } else { - return TS->font_get_spacing_space(rid); - } +int FontData::get_cache_count() const { + return cache.size(); } -void FontData::set_spacing(int p_type, int p_value) { - ERR_FAIL_COND(rid == RID()); - if (p_type == SPACING_GLYPH) { - TS->font_set_spacing_glyph(rid, p_value); - } else { - TS->font_set_spacing_space(rid, p_value); +void FontData::clear_cache() { + _clear_cache(); + cache.clear(); +} + +void FontData::remove_cache(int p_cache_index) { + ERR_FAIL_INDEX(p_cache_index, cache.size()); + if (cache[p_cache_index].is_valid()) { + TS->free(cache.write[p_cache_index]); } + cache.remove(p_cache_index); emit_changed(); } -void FontData::set_antialiased(bool p_antialiased) { - ERR_FAIL_COND(rid == RID()); - TS->font_set_antialiased(rid, p_antialiased); - emit_changed(); +Array FontData::get_size_cache_list(int p_cache_index) const { + _ensure_rid(p_cache_index); + return TS->font_get_size_cache_list(cache[p_cache_index]); } -bool FontData::get_antialiased() const { - if (rid == RID()) { - return false; - } - return TS->font_get_antialiased(rid); +void FontData::clear_size_cache(int p_cache_index) { + _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) { + _ensure_rid(p_cache_index); + TS->font_remove_size_cache(cache[p_cache_index], p_size); } -void FontData::set_distance_field_hint(bool p_distance_field) { - ERR_FAIL_COND(rid == RID()); - TS->font_set_distance_field_hint(rid, p_distance_field); +void FontData::set_variation_coordinates(int p_cache_index, const Dictionary &p_variation_coordinates) { + _ensure_rid(p_cache_index); + TS->font_set_variation_coordinates(cache[p_cache_index], p_variation_coordinates); emit_changed(); } -bool FontData::get_distance_field_hint() const { - if (rid == RID()) { - return false; - } - return TS->font_get_distance_field_hint(rid); +Dictionary FontData::get_variation_coordinates(int p_cache_index) const { + _ensure_rid(p_cache_index); + return TS->font_get_variation_coordinates(cache[p_cache_index]); } -void FontData::set_hinting(TextServer::Hinting p_hinting) { - ERR_FAIL_COND(rid == RID()); - TS->font_set_hinting(rid, p_hinting); - emit_changed(); +void FontData::set_ascent(int p_cache_index, int p_size, real_t p_ascent) { + _ensure_rid(p_cache_index); + TS->font_set_ascent(cache[p_cache_index], p_size, p_ascent); } -TextServer::Hinting FontData::get_hinting() const { - if (rid == RID()) { - return TextServer::HINTING_NONE; - } - return TS->font_get_hinting(rid); +real_t FontData::get_ascent(int p_cache_index, int p_size) const { + _ensure_rid(p_cache_index); + return TS->font_get_ascent(cache[p_cache_index], p_size); } -void FontData::set_force_autohinter(bool p_enabeld) { - ERR_FAIL_COND(rid == RID()); - TS->font_set_force_autohinter(rid, p_enabeld); - emit_changed(); +void FontData::set_descent(int p_cache_index, int p_size, real_t p_descent) { + _ensure_rid(p_cache_index); + TS->font_set_descent(cache[p_cache_index], p_size, p_descent); } -bool FontData::get_force_autohinter() const { - if (rid == RID()) { - return false; - } - return TS->font_get_force_autohinter(rid); +real_t FontData::get_descent(int p_cache_index, int p_size) const { + _ensure_rid(p_cache_index); + return TS->font_get_descent(cache[p_cache_index], p_size); } -bool FontData::has_char(char32_t p_char) const { - if (rid == RID()) { - return false; - } - return TS->font_has_char(rid, p_char); +void FontData::set_underline_position(int p_cache_index, int p_size, real_t p_underline_position) { + _ensure_rid(p_cache_index); + TS->font_set_underline_position(cache[p_cache_index], p_size, p_underline_position); } -String FontData::get_supported_chars() const { - ERR_FAIL_COND_V(rid == RID(), String()); - return TS->font_get_supported_chars(rid); +real_t FontData::get_underline_position(int p_cache_index, int p_size) const { + _ensure_rid(p_cache_index); + return TS->font_get_underline_position(cache[p_cache_index], p_size); } -Vector2 FontData::get_glyph_advance(uint32_t p_index, int p_size) const { - ERR_FAIL_COND_V(rid == RID(), Vector2()); - return TS->font_get_glyph_advance(rid, p_index, (p_size < 0) ? base_size : p_size); +void FontData::set_underline_thickness(int p_cache_index, int p_size, real_t p_underline_thickness) { + _ensure_rid(p_cache_index); + TS->font_set_underline_thickness(cache[p_cache_index], p_size, p_underline_thickness); } -Vector2 FontData::get_glyph_kerning(uint32_t p_index_a, uint32_t p_index_b, int p_size) const { - ERR_FAIL_COND_V(rid == RID(), Vector2()); - return TS->font_get_glyph_kerning(rid, p_index_a, p_index_b, (p_size < 0) ? base_size : p_size); +real_t FontData::get_underline_thickness(int p_cache_index, int p_size) const { + _ensure_rid(p_cache_index); + return TS->font_get_underline_thickness(cache[p_cache_index], p_size); } -bool FontData::has_outline() const { - if (rid == RID()) { - return false; - } - return TS->font_has_outline(rid); +void FontData::set_scale(int p_cache_index, int p_size, real_t p_scale) { + _ensure_rid(p_cache_index); + TS->font_set_scale(cache[p_cache_index], p_size, p_scale); } -float FontData::get_base_size() const { - return base_size; +real_t FontData::get_scale(int p_cache_index, int p_size) const { + _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) { + _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 { + _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 { + _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) { + _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) { + _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) { + _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 { + _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) { + _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 { + _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 { + _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) { + _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) { + _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) { + _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 { + _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) { + _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 { + _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) { + _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 { + _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) { + _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 { + _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) { + _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 { + _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 { + _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) { + _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) { + _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) { + _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 { + _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) { + _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) { + _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 { + _ensure_rid(p_cache_index); + return cache[p_cache_index]; } bool FontData::is_language_supported(const String &p_language) const { - if (rid == RID()) { - return false; - } - return TS->font_is_language_supported(rid, p_language); + _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) { - ERR_FAIL_COND(rid == RID()); - TS->font_set_language_support_override(rid, p_language, p_supported); - emit_changed(); + _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 { - if (rid == RID()) { - return false; - } - return TS->font_get_language_support_override(rid, p_language); + _ensure_rid(0); + return TS->font_get_language_support_override(cache[0], p_language); } void FontData::remove_language_support_override(const String &p_language) { - ERR_FAIL_COND(rid == RID()); - TS->font_remove_language_support_override(rid, p_language); - emit_changed(); + _ensure_rid(0); + TS->font_remove_language_support_override(cache[0], p_language); } Vector<String> FontData::get_language_support_overrides() const { - if (rid == RID()) { - return Vector<String>(); - } - return TS->font_get_language_support_overrides(rid); + _ensure_rid(0); + return TS->font_get_language_support_overrides(cache[0]); } bool FontData::is_script_supported(const String &p_script) const { - if (rid == RID()) { - return false; - } - return TS->font_is_script_supported(rid, p_script); + _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) { - ERR_FAIL_COND(rid == RID()); - TS->font_set_script_support_override(rid, p_script, p_supported); - emit_changed(); + _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 { - if (rid == RID()) { - return false; - } - return TS->font_get_script_support_override(rid, p_script); + _ensure_rid(0); + return TS->font_get_script_support_override(cache[0], p_script); } void FontData::remove_script_support_override(const String &p_script) { - ERR_FAIL_COND(rid == RID()); - TS->font_remove_script_support_override(rid, p_script); - emit_changed(); + _ensure_rid(0); + TS->font_remove_script_support_override(cache[0], p_script); } Vector<String> FontData::get_script_support_overrides() const { - if (rid == RID()) { - return Vector<String>(); - } - return TS->font_get_script_support_overrides(rid); + _ensure_rid(0); + return TS->font_get_script_support_overrides(cache[0]); } -uint32_t FontData::get_glyph_index(char32_t p_char, char32_t p_variation_selector) const { - ERR_FAIL_COND_V(rid == RID(), 0); - return TS->font_get_glyph_index(rid, p_char, p_variation_selector); +bool FontData::has_char(char32_t p_char) const { + _ensure_rid(0); + return TS->font_has_char(cache[0], p_char); } -Vector2 FontData::draw_glyph(RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const { - ERR_FAIL_COND_V(rid == RID(), Vector2()); - return TS->font_draw_glyph(rid, p_canvas, (p_size <= 0) ? base_size : p_size, p_pos, p_index, p_color); +String FontData::get_supported_chars() const { + _ensure_rid(0); + return TS->font_get_supported_chars(cache[0]); } -Vector2 FontData::draw_glyph_outline(RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color) const { - ERR_FAIL_COND_V(rid == RID(), Vector2()); - return TS->font_draw_glyph_outline(rid, p_canvas, (p_size <= 0) ? base_size : p_size, p_outline_size, p_pos, p_index, p_color); +int32_t FontData::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); } -FontData::FontData() {} +Dictionary FontData::get_supported_feature_list() const { + _ensure_rid(0); + return TS->font_supported_feature_list(cache[0]); +} -FontData::FontData(const String &p_filename, int p_base_size) { - load_resource(p_filename, p_base_size); +Dictionary FontData::get_supported_variation_list() const { + _ensure_rid(0); + return TS->font_supported_variation_list(cache[0]); } -FontData::FontData(const PackedByteArray &p_data, const String &p_type, int p_base_size) { - _load_memory(p_data, p_type, p_base_size); +FontData::FontData() { + /* NOP */ } FontData::~FontData() { - if (rid != RID()) { - TS->free(rid); - } + _clear_cache(); } /*************************************************************************/ +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. + for (int i = 0; i < rids.size(); i++) { + if (!rids[i].is_valid() && data[i].is_valid()) { + rids.write[i] = data[i]->find_cache(variation_coordinates); + } + } +} + 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_base_size", "size"), &Font::set_base_size); + ClassDB::bind_method(D_METHOD("get_base_size"), &Font::get_base_size); + ADD_PROPERTY(PropertyInfo(Variant::INT, "base_size"), "set_base_size", "get_base_size"); + + 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); + + 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("get_height", "size"), &Font::get_height, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("get_ascent", "size"), &Font::get_ascent, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("get_descent", "size"), &Font::get_descent, DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("get_underline_position", "size"), &Font::get_underline_position, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("get_underline_thickness", "size"), &Font::get_underline_thickness, DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("get_spacing", "type"), &Font::get_spacing); - ClassDB::bind_method(D_METHOD("set_spacing", "type", "value"), &Font::set_spacing); - - ClassDB::bind_method(D_METHOD("get_string_size", "text", "size"), &Font::get_string_size, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("get_string_size", "text", "size", "align", "width", "flags"), &Font::get_string_size, DEFVAL(-1), DEFVAL(HALIGN_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(-1), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND)); ClassDB::bind_method(D_METHOD("draw_string", "canvas_item", "pos", "text", "align", "width", "size", "modulate", "outline_size", "outline_modulate", "flags"), &Font::draw_string, DEFVAL(HALIGN_LEFT), DEFVAL(-1), DEFVAL(-1), 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", "align", "width", "max_lines", "size", "modulate", "outline_size", "outline_modulate", "flags"), &Font::draw_multiline_string, DEFVAL(HALIGN_LEFT), DEFVAL(-1), DEFVAL(-1), DEFVAL(-1), 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("has_char", "char"), &Font::has_char); - ClassDB::bind_method(D_METHOD("get_supported_chars"), &Font::get_supported_chars); - ClassDB::bind_method(D_METHOD("get_char_size", "char", "next", "size"), &Font::get_char_size, DEFVAL(0), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("draw_char", "canvas_item", "pos", "char", "next", "size", "modulate", "outline_size", "outline_modulate"), &Font::draw_char, DEFVAL(0), DEFVAL(-1), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(1, 1, 1, 0))); - ClassDB::bind_method(D_METHOD("update_changes"), &Font::update_changes); - - ADD_GROUP("Extra Spacing", "extra_spacing"); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "extra_spacing_top"), "set_spacing", "get_spacing", SPACING_TOP); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "extra_spacing_bottom"), "set_spacing", "get_spacing", SPACING_BOTTOM); - - BIND_ENUM_CONSTANT(SPACING_TOP); - BIND_ENUM_CONSTANT(SPACING_BOTTOM); -} - -void Font::_data_changed() { - cache.clear(); - cache_wrap.clear(); + ClassDB::bind_method(D_METHOD("has_char", "char"), &Font::has_char); + ClassDB::bind_method(D_METHOD("get_supported_chars"), &Font::get_supported_chars); - emit_changed(); - notify_property_list_changed(); + ClassDB::bind_method(D_METHOD("update_changes"), &Font::update_changes); } bool Font::_set(const StringName &p_name, const Variant &p_value) { - String str = p_name; + Vector<String> tokens = p_name.operator String().split("/"); #ifndef DISABLE_DEPRECATED - if (str == "font_data") { // Compatibility, DynamicFont main data + 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 (str.begins_with("fallback/")) { // Compatibility, DynamicFont fallback data + } 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 (str == "fallback") { // Compatibility, BitmapFont fallback + } 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++) { @@ -612,10 +1091,9 @@ bool Font::_set(const StringName &p_name, const Variant &p_value) { return false; } #endif /* DISABLE_DEPRECATED */ - if (str.begins_with("data/")) { - int idx = str.get_slicec('/', 1).to_int(); + 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); @@ -631,14 +1109,13 @@ bool Font::_set(const StringName &p_name, const Variant &p_value) { return true; } } - return false; } bool Font::_get(const StringName &p_name, Variant &r_ret) const { - String str = p_name; - if (str.begins_with("data/")) { - int idx = str.get_slicec('/', 1).to_int(); + Vector<String> tokens = p_name.operator String().split("/"); + if (tokens.size() == 2 && tokens[0] == "data") { + int idx = tokens[1].to_int(); if (idx == data.size()) { r_ret = Ref<FontData>(); @@ -656,24 +1133,44 @@ 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")); } - p_list->push_back(PropertyInfo(Variant::OBJECT, "data/" + itos(data.size()), PROPERTY_HINT_RESOURCE_TYPE, "FontData")); } void Font::reset_state() { - spacing_top = 0; - spacing_bottom = 0; + 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); + } + } cache.clear(); cache_wrap.clear(); data.clear(); + rids.clear(); + + base_size = 16; + 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]; + } + } + 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()); if (data[data.size() - 1].is_valid()) { - data.write[data.size() - 1]->connect("changed", callable_mp(this, &Font::_data_changed), varray(), CONNECT_REFERENCE_COUNTED); + data.write[data.size() - 1]->connect(SNAME("changed"), callable_mp(this, &Font::_data_changed), varray(), CONNECT_REFERENCE_COUNTED); } cache.clear(); @@ -688,13 +1185,14 @@ void Font::set_data(int p_idx, const Ref<FontData> &p_data) { ERR_FAIL_INDEX(p_idx, data.size()); if (data[p_idx].is_valid()) { - data.write[p_idx]->disconnect("changed", callable_mp(this, &Font::_data_changed)); + data.write[p_idx]->disconnect(SNAME("changed"), callable_mp(this, &Font::_data_changed)); } data.write[p_idx] = p_data; + rids.write[p_idx] = RID(); if (data[p_idx].is_valid()) { - data.write[p_idx]->connect("changed", callable_mp(this, &Font::_data_changed), varray(), CONNECT_REFERENCE_COUNTED); + data.write[p_idx]->connect(SNAME("changed"), callable_mp(this, &Font::_data_changed), varray(), CONNECT_REFERENCE_COUNTED); } cache.clear(); @@ -713,14 +1211,31 @@ Ref<FontData> Font::get_data(int p_idx) const { 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]; +} + +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); + } + } + 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("changed", callable_mp(this, &Font::_data_changed)); + data.write[p_idx]->disconnect(SNAME("changed"), callable_mp(this, &Font::_data_changed)); } data.remove(p_idx); + rids.remove(p_idx); cache.clear(); cache_wrap.clear(); @@ -729,117 +1244,148 @@ void Font::remove_data(int p_idx) { notify_property_list_changed(); } -Dictionary Font::get_feature_list() const { - Dictionary out; - for (int i = 0; i < data.size(); i++) { - Dictionary data_ftrs = data[i]->get_feature_list(); - for (const Variant *ftr = data_ftrs.next(nullptr); ftr != nullptr; ftr = data_ftrs.next(ftr)) { - out[*ftr] = data_ftrs[*ftr]; - } +void Font::set_base_size(int p_size) { + base_size = p_size; +} + +int Font::get_base_size() const { + return base_size; +} + +void Font::set_variation_coordinates(const Dictionary &p_variation_coordinates) { + _data_changed(); + variation_coordinates = p_variation_coordinates; +} + +Dictionary Font::get_variation_coordinates() const { + return variation_coordinates; +} + +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; + } +} + +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 out; } -float Font::get_height(int p_size) const { - float ret = 0.f; +real_t Font::get_height(int p_size) const { + int size = (p_size <= 0) ? base_size : p_size; + real_t ret = 0.f; for (int i = 0; i < data.size(); i++) { - ret = MAX(ret, data[i]->get_height(p_size)); + _ensure_rid(i); + ret = MAX(ret, TS->font_get_ascent(rids[i], size) + TS->font_get_descent(rids[i], size)); } - return ret + spacing_top + spacing_bottom; + return ret + spacing_bottom + spacing_top; } -float Font::get_ascent(int p_size) const { - float ret = 0.f; +real_t Font::get_ascent(int p_size) const { + int size = (p_size <= 0) ? base_size : p_size; + real_t ret = 0.f; for (int i = 0; i < data.size(); i++) { - ret = MAX(ret, data[i]->get_ascent(p_size)); + _ensure_rid(i); + ret = MAX(ret, TS->font_get_ascent(rids[i], size)); } return ret + spacing_top; } -float Font::get_descent(int p_size) const { - float ret = 0.f; +real_t Font::get_descent(int p_size) const { + int size = (p_size <= 0) ? base_size : p_size; + real_t ret = 0.f; for (int i = 0; i < data.size(); i++) { - ret = MAX(ret, data[i]->get_descent(p_size)); + _ensure_rid(i); + ret = MAX(ret, TS->font_get_descent(rids[i], size)); } return ret + spacing_bottom; } -float Font::get_underline_position(int p_size) const { - float ret = 0.f; +real_t Font::get_underline_position(int p_size) const { + int size = (p_size <= 0) ? base_size : p_size; + real_t ret = 0.f; for (int i = 0; i < data.size(); i++) { - ret = MAX(ret, data[i]->get_underline_position(p_size)); + _ensure_rid(i); + ret = MAX(ret, TS->font_get_underline_position(rids[i], size)); } - return ret; + return ret + spacing_top; } -float Font::get_underline_thickness(int p_size) const { - float ret = 0.f; +real_t Font::get_underline_thickness(int p_size) const { + int size = (p_size <= 0) ? base_size : p_size; + real_t ret = 0.f; for (int i = 0; i < data.size(); i++) { - ret = MAX(ret, data[i]->get_underline_thickness(p_size)); + _ensure_rid(i); + ret = MAX(ret, TS->font_get_underline_thickness(rids[i], size)); } return ret; } -int Font::get_spacing(int p_type) const { - if (p_type == SPACING_TOP) { - return spacing_top; - } else if (p_type == SPACING_BOTTOM) { - return spacing_bottom; - } +Size2 Font::get_string_size(const String &p_text, int p_size, HAlign p_align, real_t p_width, uint8_t p_flags) const { + ERR_FAIL_COND_V(data.is_empty(), Size2()); - return 0; -} + int size = (p_size <= 0) ? base_size : p_size; -void Font::set_spacing(int p_type, int p_value) { - if (p_type == SPACING_TOP) { - spacing_top = p_value; - } else if (p_type == SPACING_BOTTOM) { - spacing_bottom = p_value; + for (int i = 0; i < data.size(); i++) { + _ensure_rid(i); } - emit_changed(); - notify_property_list_changed(); -} - -// Drawing string and string sizes, cached. - -Size2 Font::get_string_size(const String &p_text, int p_size) const { - ERR_FAIL_COND_V(data.is_empty(), Size2()); - uint64_t hash = p_text.hash64(); - hash = hash_djb2_one_64(p_size, hash); + if (p_align == HALIGN_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(size, hash); Ref<TextLine> buffer; if (cache.has(hash)) { buffer = cache.get(hash); } else { buffer.instantiate(); - int size = p_size <= 0 ? data[0]->get_base_size() : p_size; buffer->add_string(p_text, Ref<Font>(this), size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); cache.insert(hash, buffer); } - if (buffer->get_orientation() == TextServer::ORIENTATION_HORIZONTAL) { - return buffer->get_size() + Vector2(0, spacing_top + spacing_bottom); - } else { - return buffer->get_size() + Vector2(spacing_top + spacing_bottom, 0); - } + return buffer->get_size(); } -Size2 Font::get_multiline_string_size(const String &p_text, float p_width, int p_size, uint8_t p_flags) const { +Size2 Font::get_multiline_string_size(const String &p_text, real_t p_width, int p_size, uint8_t p_flags) const { ERR_FAIL_COND_V(data.is_empty(), Size2()); - uint64_t hash = p_text.hash64(); - hash = hash_djb2_one_64(p_size, hash); + int size = (p_size <= 0) ? base_size : p_size; + 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(size, wrp_hash); Ref<TextParagraph> lines_buffer; if (cache_wrap.has(wrp_hash)) { lines_buffer = cache_wrap.get(wrp_hash); } else { lines_buffer.instantiate(); - int size = p_size <= 0 ? data[0]->get_base_size() : p_size; lines_buffer->add_string(p_text, Ref<Font>(this), size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); lines_buffer->set_width(p_width); lines_buffer->set_flags(p_flags); @@ -851,40 +1397,50 @@ Size2 Font::get_multiline_string_size(const String &p_text, float p_width, int p 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 + spacing_top + spacing_bottom; + ret.y += line_size.y; } else { ret.y = MAX(ret.y, line_size.y); - ret.x += line_size.x + spacing_top + spacing_bottom; + ret.x += line_size.x; } } return ret; } -void Font::draw_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HAlign p_align, float p_width, int p_size, const Color &p_modulate, int p_outline_size, const Color &p_outline_modulate, uint8_t p_flags) const { +void Font::draw_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HAlign p_align, real_t p_width, int p_size, const Color &p_modulate, int p_outline_size, const Color &p_outline_modulate, uint8_t p_flags) const { ERR_FAIL_COND(data.is_empty()); + int size = (p_size <= 0) ? base_size : p_size; + + for (int i = 0; i < data.size(); i++) { + _ensure_rid(i); + } + uint64_t hash = p_text.hash64(); - hash = hash_djb2_one_64(p_size, hash); + if (p_align == HALIGN_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(size, hash); Ref<TextLine> buffer; if (cache.has(hash)) { buffer = cache.get(hash); } else { buffer.instantiate(); - int size = p_size <= 0 ? data[0]->get_base_size() : p_size; buffer->add_string(p_text, Ref<Font>(this), 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 += spacing_top - buffer->get_line_ascent(); + ofs.y -= buffer->get_line_ascent(); } else { - ofs.x += spacing_top - buffer->get_line_ascent(); + ofs.x -= buffer->get_line_ascent(); } buffer->set_width(p_width); buffer->set_align(p_align); + 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); @@ -895,18 +1451,22 @@ void Font::draw_string(RID p_canvas_item, const Point2 &p_pos, const String &p_t void Font::draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HAlign p_align, float p_width, int p_max_lines, int p_size, const Color &p_modulate, int p_outline_size, const Color &p_outline_modulate, uint8_t p_flags) const { ERR_FAIL_COND(data.is_empty()); - uint64_t hash = p_text.hash64(); - hash = hash_djb2_one_64(p_size, hash); + int size = (p_size <= 0) ? base_size : p_size; + 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(size, wrp_hash); Ref<TextParagraph> lines_buffer; if (cache_wrap.has(wrp_hash)) { lines_buffer = cache_wrap.get(wrp_hash); } else { lines_buffer.instantiate(); - int size = p_size <= 0 ? data[0]->get_base_size() : p_size; lines_buffer->add_string(p_text, Ref<Font>(this), size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); lines_buffer->set_width(p_width); lines_buffer->set_flags(p_flags); @@ -918,12 +1478,10 @@ void Font::draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const S Vector2 lofs = p_pos; for (int i = 0; i < lines_buffer->get_line_count(); i++) { if (lines_buffer->get_orientation() == TextServer::ORIENTATION_HORIZONTAL) { - lofs.y += spacing_top; if (i == 0) { lofs.y -= lines_buffer->get_line_ascent(0); } } else { - lofs.x += spacing_top; if (i == 0) { lofs.x -= lines_buffer->get_line_ascent(0); } @@ -939,9 +1497,9 @@ void Font::draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const S Size2 line_size = lines_buffer->get_line_size(i); if (lines_buffer->get_orientation() == TextServer::ORIENTATION_HORIZONTAL) { - lofs.y += line_size.y + spacing_bottom; + lofs.y += line_size.y; } else { - lofs.x += line_size.x + spacing_bottom; + lofs.x += line_size.x; } if ((p_max_lines > 0) && (i >= p_max_lines)) { @@ -950,37 +1508,17 @@ void Font::draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const S } } -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; -} - -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]; - } - } - } - return chars; -} - Size2 Font::get_char_size(char32_t p_char, char32_t p_next, int p_size) const { + int size = (p_size <= 0) ? base_size : p_size; + for (int i = 0; i < data.size(); i++) { + _ensure_rid(i); if (data[i]->has_char(p_char)) { - int size = p_size <= 0 ? data[i]->get_base_size() : p_size; - uint32_t glyph_a = data[i]->get_glyph_index(p_char); - Size2 ret = Size2(data[i]->get_glyph_advance(glyph_a, size).x, data[i]->get_height(size)); + int32_t glyph_a = TS->font_get_glyph_index(rids[i], size, p_char, 0); + Size2 ret = Size2(TS->font_get_glyph_advance(rids[i], size, glyph_a).x, TS->font_get_ascent(rids[i], size) + TS->font_get_descent(rids[i], size)); if ((p_next != 0) && data[i]->has_char(p_next)) { - uint32_t glyph_b = data[i]->get_glyph_index(p_next); - ret.x -= data[i]->get_glyph_kerning(glyph_a, glyph_b, size).x; + int32_t glyph_b = TS->font_get_glyph_index(rids[i], size, p_next, 0); + ret.x -= TS->font_get_kerning(rids[i], size, Vector2i(glyph_a, glyph_b)).x; } return ret; } @@ -988,35 +1526,55 @@ Size2 Font::get_char_size(char32_t p_char, char32_t p_next, int p_size) const { return Size2(); } -float 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 { +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 { + int size = (p_size <= 0) ? base_size : p_size; + for (int i = 0; i < data.size(); i++) { + _ensure_rid(i); if (data[i]->has_char(p_char)) { - int size = p_size <= 0 ? data[i]->get_base_size() : p_size; - uint32_t glyph_a = data[i]->get_glyph_index(p_char); - float ret = data[i]->get_glyph_advance(glyph_a, size).x; + int32_t glyph_a = TS->font_get_glyph_index(rids[i], size, p_char, 0); + real_t ret = TS->font_get_glyph_advance(rids[i], size, glyph_a).x; if ((p_next != 0) && data[i]->has_char(p_next)) { - uint32_t glyph_b = data[i]->get_glyph_index(p_next); - ret -= data[i]->get_glyph_kerning(glyph_a, glyph_b, size).x; + int32_t glyph_b = TS->font_get_glyph_index(rids[i], size, p_next, 0); + ret -= TS->font_get_kerning(rids[i], size, Vector2i(glyph_a, glyph_b)).x; } + if (p_outline_size > 0 && p_outline_modulate.a != 0.0f) { - data[i]->draw_glyph_outline(p_canvas_item, size, p_outline_size, p_pos, glyph_a, p_outline_modulate); + TS->font_draw_glyph_outline(rids[i], p_canvas_item, size, p_outline_size, p_pos, glyph_a, p_outline_modulate); } - data[i]->draw_glyph(p_canvas_item, size, p_pos, glyph_a, p_modulate); + TS->font_draw_glyph(rids[i], p_canvas_item, size, p_pos, glyph_a, p_modulate); return ret; } } return 0; } -Vector<RID> Font::get_rids() const { - Vector<RID> ret; +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; +} + +String Font::get_supported_chars() const { + String chars; for (int i = 0; i < data.size(); i++) { - RID rid = data[i]->get_rid(); - if (rid != RID()) { - ret.push_back(rid); + 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]; + } } } - return ret; + return chars; +} + +Vector<RID> Font::get_rids() const { + for (int i = 0; i < data.size(); i++) { + _ensure_rid(i); + } + return rids; } void Font::update_changes() { @@ -1029,103 +1587,7 @@ Font::Font() { } Font::~Font() { + clear_data(); cache.clear(); cache_wrap.clear(); } - -/*************************************************************************/ - -RES ResourceFormatLoaderFont::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; - } - - Ref<FontData> dfont; - dfont.instantiate(); - dfont->load_resource(p_path); - - if (r_error) { - *r_error = OK; - } - - return dfont; -} - -void ResourceFormatLoaderFont::get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const { -#ifndef DISABLE_DEPRECATED - if (p_type == "DynamicFontData") { - p_extensions->push_back("ttf"); - p_extensions->push_back("otf"); - p_extensions->push_back("woff"); - return; - } - if (p_type == "BitmapFont") { // BitmapFont (*.font, *fnt) is handled by ResourceFormatLoaderCompatFont - return; - } -#endif /* DISABLE_DEPRECATED */ - if (p_type == "" || handles_type(p_type)) { - get_recognized_extensions(p_extensions); - } -} - -void ResourceFormatLoaderFont::get_recognized_extensions(List<String> *p_extensions) const { - p_extensions->push_back("ttf"); - p_extensions->push_back("otf"); - p_extensions->push_back("woff"); - p_extensions->push_back("font"); - p_extensions->push_back("fnt"); -} - -bool ResourceFormatLoaderFont::handles_type(const String &p_type) const { - return (p_type == "FontData"); -} - -String ResourceFormatLoaderFont::get_resource_type(const String &p_path) const { - String el = p_path.get_extension().to_lower(); - if (el == "ttf" || el == "otf" || el == "woff" || el == "font" || el == "fnt") { - return "FontData"; - } - return ""; -} - -#ifndef DISABLE_DEPRECATED - -RES ResourceFormatLoaderCompatFont::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; - } - - Ref<FontData> dfont; - dfont.instantiate(); - dfont->load_resource(p_path); - - Ref<Font> font; - font.instantiate(); - font->add_data(dfont); - - if (r_error) { - *r_error = OK; - } - - return font; -} - -void ResourceFormatLoaderCompatFont::get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const { - if (p_type == "BitmapFont") { - p_extensions->push_back("font"); - p_extensions->push_back("fnt"); - } -} - -void ResourceFormatLoaderCompatFont::get_recognized_extensions(List<String> *p_extensions) const { -} - -bool ResourceFormatLoaderCompatFont::handles_type(const String &p_type) const { - return (p_type == "Font"); -} - -String ResourceFormatLoaderCompatFont::get_resource_type(const String &p_path) const { - return ""; -} - -#endif /* DISABLE_DEPRECATED */ diff --git a/scene/resources/font.h b/scene/resources/font.h index 200373aa8c..9a34edce64 100644 --- a/scene/resources/font.h +++ b/scene/resources/font.h @@ -41,17 +41,27 @@ class FontData : public Resource { GDCLASS(FontData, Resource); + RES_BASE_EXTENSION("fontdata"); -public: - enum SpacingType { - SPACING_GLYPH, - SPACING_SPACE, - }; + // Font source data. + const uint8_t *data_ptr = nullptr; + size_t data_size = 0; + PackedByteArray data; -private: - RID rid; - int base_size = 16; - String path; + bool antialiased = true; + bool msdf = false; + int msdf_pixel_range = 16; + int msdf_size = 48; + int fixed_size = 0; + bool force_autohinter = false; + TextServer::Hinting hinting = TextServer::HINTING_LIGHT; + real_t oversampling = 0.f; + + // Cache. + mutable Vector<RID> cache; + + _FORCE_INLINE_ void _clear_cache(); + _FORCE_INLINE_ void _ensure_rid(int p_cache_index) const; protected: static void _bind_methods(); @@ -63,79 +73,132 @@ protected: virtual void reset_state() override; public: - virtual RID get_rid() const override; + // Font source data. + virtual void set_data_ptr(const uint8_t *p_data, size_t p_size); + virtual void set_data(const PackedByteArray &p_data); + virtual PackedByteArray get_data() const; - void load_resource(const String &p_filename, int p_base_size = 16); - void load_memory(const uint8_t *p_data, size_t p_size, const String &p_type, int p_base_size = 16); - void _load_memory(const PackedByteArray &p_data, const String &p_type, int p_base_size = 16); + // Common properties. + virtual void set_antialiased(bool p_antialiased); + virtual bool is_antialiased() const; - void new_bitmap(float p_height, float p_ascent, int p_base_size = 16); + virtual void set_multichannel_signed_distance_field(bool p_msdf); + virtual bool is_multichannel_signed_distance_field() const; - void bitmap_add_texture(const Ref<Texture> &p_texture); - void bitmap_add_char(char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance); - void bitmap_add_kerning_pair(char32_t p_A, char32_t p_B, int p_kerning); + virtual void set_msdf_pixel_range(int p_msdf_pixel_range); + virtual int get_msdf_pixel_range() const; - void set_data_path(const String &p_path); - String get_data_path() const; + virtual void set_msdf_size(int p_msdf_size); + virtual int get_msdf_size() const; - float get_height(int p_size) const; - float get_ascent(int p_size) const; - float get_descent(int p_size) const; + virtual void set_fixed_size(int p_fixed_size); + virtual int get_fixed_size() const; - Dictionary get_feature_list() const; - Dictionary get_variation_list() const; + virtual void set_force_autohinter(bool p_force_autohinter); + virtual bool is_force_autohinter() const; - void set_variation(const String &p_name, double p_value); - double get_variation(const String &p_name) const; + virtual void set_hinting(TextServer::Hinting p_hinting); + virtual TextServer::Hinting get_hinting() const; - float get_underline_position(int p_size) const; - float get_underline_thickness(int p_size) const; + virtual void set_oversampling(real_t p_oversampling); + virtual real_t get_oversampling() const; - int get_spacing(int p_type) const; - void set_spacing(int p_type, int p_value); + // Cache. + virtual RID find_cache(const Dictionary &p_variation_coordinates) const; - void set_antialiased(bool p_antialiased); - bool get_antialiased() const; + virtual int get_cache_count() const; + virtual void clear_cache(); + virtual void remove_cache(int p_cache_index); - void set_distance_field_hint(bool p_distance_field); - bool get_distance_field_hint() const; + virtual Array get_size_cache_list(int p_cache_index) const; + virtual void clear_size_cache(int p_cache_index); + virtual void remove_size_cache(int p_cache_index, const Vector2i &p_size); - void set_force_autohinter(bool p_enabeld); - bool get_force_autohinter() const; + virtual void set_variation_coordinates(int p_cache_index, const Dictionary &p_variation_coordinates); + virtual Dictionary get_variation_coordinates(int p_cache_index) const; - void set_hinting(TextServer::Hinting p_hinting); - TextServer::Hinting get_hinting() 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; - bool has_char(char32_t p_char) const; - String get_supported_chars() 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; - Vector2 get_glyph_advance(uint32_t p_index, int p_size) const; - Vector2 get_glyph_kerning(uint32_t p_index_a, uint32_t p_index_b, int p_size) 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; - bool has_outline() const; - float get_base_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; - bool is_language_supported(const String &p_language) const; - void set_language_support_override(const String &p_language, bool p_supported); - bool get_language_support_override(const String &p_language) const; - void remove_language_support_override(const String &p_language); - Vector<String> get_language_support_overrides() 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; - bool is_script_supported(const String &p_script) const; - void set_script_support_override(const String &p_script, bool p_supported); - bool get_script_support_override(const String &p_script) const; - void remove_script_support_override(const String &p_script); - Vector<String> get_script_support_overrides() 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; - uint32_t get_glyph_index(char32_t p_char, char32_t p_variation_selector = 0x0000) 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); + virtual void remove_texture(int p_cache_index, const Vector2i &p_size, int p_texture_index); - Vector2 draw_glyph(RID p_canvas, int p_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color = Color(1, 1, 1)) const; - Vector2 draw_glyph_outline(RID p_canvas, int p_size, int p_outline_size, const Vector2 &p_pos, uint32_t p_index, const Color &p_color = Color(1, 1, 1)) const; + virtual void set_texture_image(int p_cache_index, const Vector2i &p_size, int p_texture_index, const Ref<Image> &p_image); + virtual Ref<Image> get_texture_image(int p_cache_index, const Vector2i &p_size, int p_texture_index) const; - FontData(); - FontData(const String &p_filename, int p_base_size); - FontData(const PackedByteArray &p_data, const String &p_type, int p_base_size); + virtual void set_texture_offsets(int p_cache_index, const Vector2i &p_size, int p_texture_index, const PackedInt32Array &p_offset); + virtual PackedInt32Array get_texture_offsets(int p_cache_index, const Vector2i &p_size, int p_texture_index) const; + + virtual Array get_glyph_list(int p_cache_index, const Vector2i &p_size) const; + virtual void clear_glyphs(int p_cache_index, const Vector2i &p_size); + virtual void remove_glyph(int p_cache_index, const Vector2i &p_size, int32_t p_glyph); + + virtual void set_glyph_advance(int p_cache_index, int p_size, int32_t p_glyph, const Vector2 &p_advance); + virtual Vector2 get_glyph_advance(int p_cache_index, int p_size, int32_t p_glyph) const; + + virtual void set_glyph_offset(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_offset); + virtual Vector2 get_glyph_offset(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const; + + virtual void set_glyph_size(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_gl_size); + virtual Vector2 get_glyph_size(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const; + + virtual void set_glyph_uv_rect(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, const Rect2 &p_uv_rect); + virtual Rect2 get_glyph_uv_rect(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const; + + virtual void set_glyph_texture_idx(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, int p_texture_idx); + virtual int get_glyph_texture_idx(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const; + virtual Array get_kerning_list(int p_cache_index, int p_size) const; + virtual void clear_kerning_map(int p_cache_index, int p_size); + virtual void remove_kerning(int p_cache_index, int p_size, const Vector2i &p_glyph_pair); + + virtual void set_kerning(int p_cache_index, int p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning); + virtual Vector2 get_kerning(int p_cache_index, int p_size, const Vector2i &p_glyph_pair) const; + + 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); + virtual Vector<String> get_script_support_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(); }; @@ -147,20 +210,22 @@ class TextParagraph; class Font : public Resource { GDCLASS(Font, Resource); -public: - enum SpacingType { - SPACING_TOP, - SPACING_BOTTOM, - }; - -private: - int spacing_top = 0; - int spacing_bottom = 0; - + // Shaped string cache. mutable LRUCache<uint64_t, Ref<TextLine>> cache; mutable LRUCache<uint64_t, Ref<TextParagraph>> cache_wrap; + // Font data cache. Vector<Ref<FontData>> data; + mutable Vector<RID> rids; + + // Font config. + int base_size = 16; + Dictionary variation_coordinates; + int spacing_bottom = 0; + int spacing_top = 0; + + _FORCE_INLINE_ void _data_changed(); + _FORCE_INLINE_ void _ensure_rid(int p_index) const; // Find or create cache record. protected: static void _bind_methods(); @@ -171,41 +236,49 @@ protected: virtual void reset_state() override; - void _data_changed(); - public: Dictionary get_feature_list() const; - // Font data control. - void add_data(const Ref<FontData> &p_data); - void set_data(int p_idx, const Ref<FontData> &p_data); - int get_data_count() const; - Ref<FontData> get_data(int p_idx) const; - void remove_data(int p_idx); + // 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); + + // Font configuration. + virtual void set_base_size(int p_size); + virtual int get_base_size() const; - float get_height(int p_size = -1) const; - float get_ascent(int p_size = -1) const; - float get_descent(int p_size = -1) const; + virtual void set_variation_coordinates(const Dictionary &p_variation_coordinates); + virtual Dictionary get_variation_coordinates() const; - float get_underline_position(int p_size = -1) const; - float get_underline_thickness(int p_size = -1) const; + virtual void set_spacing(TextServer::SpacingType p_spacing, int p_value); + virtual int get_spacing(TextServer::SpacingType p_spacing) const; - int get_spacing(int p_type) const; - void set_spacing(int p_type, int p_value); + // Font metrics. + virtual real_t get_height(int p_size = -1) const; + virtual real_t get_ascent(int p_size = -1) const; + virtual real_t get_descent(int p_size = -1) const; + virtual real_t get_underline_position(int p_size = -1) const; + virtual real_t get_underline_thickness(int p_size = -1) const; // Drawing string. - Size2 get_string_size(const String &p_text, int p_size = -1) const; - Size2 get_multiline_string_size(const String &p_text, float p_width = -1, int p_size = -1, uint8_t p_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND) const; + virtual Size2 get_string_size(const String &p_text, int p_size = -1, HAlign p_align = HALIGN_LEFT, real_t p_width = -1, uint8_t p_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const; + virtual Size2 get_multiline_string_size(const String &p_text, real_t p_width = -1, int p_size = -1, uint8_t p_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND) const; - void draw_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HAlign p_align = HALIGN_LEFT, float p_width = -1, int p_size = -1, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint8_t p_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const; - void draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HAlign p_align = HALIGN_LEFT, float p_width = -1, int p_max_lines = -1, int p_size = -1, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint8_t p_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const; + virtual void draw_string(RID p_canvas_item, const Point2 &p_pos, const String &p_text, HAlign p_align = HALIGN_LEFT, real_t p_width = -1, int p_size = -1, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint8_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, HAlign p_align = HALIGN_LEFT, real_t p_width = -1, int p_max_lines = -1, int p_size = -1, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0), uint8_t p_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND) const; // Helper functions. - bool has_char(char32_t p_char) const; - String get_supported_chars() const; + virtual bool has_char(char32_t p_char) const; + virtual String get_supported_chars() const; - Size2 get_char_size(char32_t p_char, char32_t p_next = 0, int p_size = -1) const; - float draw_char(RID p_canvas_item, const Point2 &p_pos, char32_t p_char, char32_t p_next = 0, int p_size = -1, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0)) const; + // Drawing char. + virtual Size2 get_char_size(char32_t p_char, char32_t p_next = 0, int p_size = -1) 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 = -1, const Color &p_modulate = Color(1, 1, 1), int p_outline_size = 0, const Color &p_outline_modulate = Color(1, 1, 1, 0)) const; Vector<RID> get_rids() const; @@ -215,31 +288,4 @@ public: ~Font(); }; -VARIANT_ENUM_CAST(FontData::SpacingType); -VARIANT_ENUM_CAST(Font::SpacingType); - -/*************************************************************************/ - -class ResourceFormatLoaderFont : 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 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; -}; - -#ifndef DISABLE_DEPRECATED - -class ResourceFormatLoaderCompatFont : 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 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; -}; - -#endif /* DISABLE_DEPRECATED */ - #endif /* FONT_H */ diff --git a/scene/resources/height_map_shape_3d.cpp b/scene/resources/height_map_shape_3d.cpp index de5da944bc..d1a958ad38 100644 --- a/scene/resources/height_map_shape_3d.cpp +++ b/scene/resources/height_map_shape_3d.cpp @@ -41,7 +41,7 @@ Vector<Vector3> HeightMapShape3D::get_debug_mesh_lines() const { Vector2 size(map_width - 1, map_depth - 1); Vector2 start = size * -0.5; - const float *r = map_data.ptr(); + const real_t *r = map_data.ptr(); // reserve some memory for our points.. points.resize(((map_width - 1) * map_depth * 2) + (map_width * (map_depth - 1) * 2) + ((map_width - 1) * (map_depth - 1) * 2)); @@ -105,7 +105,7 @@ void HeightMapShape3D::set_map_width(int p_new) { int new_size = map_width * map_depth; map_data.resize(map_width * map_depth); - float *w = map_data.ptrw(); + real_t *w = map_data.ptrw(); while (was_size < new_size) { w[was_size++] = 0.0; } @@ -129,7 +129,7 @@ void HeightMapShape3D::set_map_depth(int p_new) { int new_size = map_width * map_depth; map_data.resize(new_size); - float *w = map_data.ptrw(); + real_t *w = map_data.ptrw(); while (was_size < new_size) { w[was_size++] = 0.0; } @@ -143,7 +143,7 @@ int HeightMapShape3D::get_map_depth() const { return map_depth; } -void HeightMapShape3D::set_map_data(PackedFloat32Array p_new) { +void HeightMapShape3D::set_map_data(Vector<real_t> p_new) { int size = (map_width * map_depth); if (p_new.size() != size) { // fail @@ -151,10 +151,10 @@ void HeightMapShape3D::set_map_data(PackedFloat32Array p_new) { } // copy - float *w = map_data.ptrw(); - const float *r = p_new.ptr(); + real_t *w = map_data.ptrw(); + const real_t *r = p_new.ptr(); for (int i = 0; i < size; i++) { - float val = r[i]; + real_t val = r[i]; w[i] = val; if (i == 0) { min_height = val; @@ -174,7 +174,7 @@ void HeightMapShape3D::set_map_data(PackedFloat32Array p_new) { notify_change_to_owners(); } -PackedFloat32Array HeightMapShape3D::get_map_data() const { +Vector<real_t> HeightMapShape3D::get_map_data() const { return map_data; } @@ -194,7 +194,7 @@ void HeightMapShape3D::_bind_methods() { HeightMapShape3D::HeightMapShape3D() : Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_HEIGHTMAP)) { map_data.resize(map_width * map_depth); - float *w = map_data.ptrw(); + real_t *w = map_data.ptrw(); w[0] = 0.0; w[1] = 0.0; w[2] = 0.0; diff --git a/scene/resources/height_map_shape_3d.h b/scene/resources/height_map_shape_3d.h index 1219791c56..1273aee040 100644 --- a/scene/resources/height_map_shape_3d.h +++ b/scene/resources/height_map_shape_3d.h @@ -38,7 +38,7 @@ class HeightMapShape3D : public Shape3D { int map_width = 2; int map_depth = 2; - PackedFloat32Array map_data; + Vector<real_t> map_data; real_t min_height = 0.0; real_t max_height = 0.0; @@ -51,8 +51,8 @@ public: int get_map_width() const; void set_map_depth(int p_new); int get_map_depth() const; - void set_map_data(PackedFloat32Array p_new); - PackedFloat32Array get_map_data() const; + void set_map_data(Vector<real_t> p_new); + Vector<real_t> get_map_data() const; virtual Vector<Vector3> get_debug_mesh_lines() const override; virtual real_t get_enclosing_radius() const override; diff --git a/scene/resources/immediate_mesh.cpp b/scene/resources/immediate_mesh.cpp index ebe65605f8..05d1a7bf94 100644 --- a/scene/resources/immediate_mesh.cpp +++ b/scene/resources/immediate_mesh.cpp @@ -146,7 +146,7 @@ void ImmediateMesh::surface_add_vertex_2d(const Vector2 &p_vertex) { } void ImmediateMesh::surface_end() { ERR_FAIL_COND_MSG(!surface_active, "Not creating any surface. Use surface_begin() to do it."); - ERR_FAIL_COND_MSG(!vertices.size(), "No vertices were added, surface cant be created."); + ERR_FAIL_COND_MSG(!vertices.size(), "No vertices were added, surface can't be created."); uint32_t format = ARRAY_FORMAT_VERTEX; diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index 4f7926d38d..77a68151c4 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -31,6 +31,7 @@ #include "material.h" #include "core/config/engine.h" +#include "core/version.h" #ifdef TOOLS_ENABLED #include "editor/editor_settings.h" @@ -130,7 +131,7 @@ bool ShaderMaterial::_set(const StringName &p_name, const Variant &p_value) { } } if (pr) { - RenderingServer::get_singleton()->material_set_param(_get_material(), pr, p_value); + set_shader_param(pr, p_value); return true; } } @@ -152,7 +153,12 @@ bool ShaderMaterial::_get(const StringName &p_name, Variant &r_ret) const { } if (pr) { - r_ret = RenderingServer::get_singleton()->material_get_param(_get_material(), pr); + const Map<StringName, Variant>::Element *E = param_cache.find(pr); + if (E) { + r_ret = E->get(); + } else { + r_ret = Variant(); + } return true; } } @@ -219,11 +225,31 @@ Ref<Shader> ShaderMaterial::get_shader() const { } void ShaderMaterial::set_shader_param(const StringName &p_param, const Variant &p_value) { - RS::get_singleton()->material_set_param(_get_material(), p_param, p_value); + if (p_value.get_type() == Variant::NIL) { + param_cache.erase(p_param); + RS::get_singleton()->material_set_param(_get_material(), p_param, Variant()); + } else { + param_cache[p_param] = p_value; + if (p_value.get_type() == Variant::OBJECT) { + RID tex_rid = p_value; + if (tex_rid == RID()) { + param_cache.erase(p_param); + RS::get_singleton()->material_set_param(_get_material(), p_param, Variant()); + } else { + RS::get_singleton()->material_set_param(_get_material(), p_param, tex_rid); + } + } else { + RS::get_singleton()->material_set_param(_get_material(), p_param, p_value); + } + } } Variant ShaderMaterial::get_shader_param(const StringName &p_param) const { - return RS::get_singleton()->material_get_param(_get_material(), p_param); + if (param_cache.has(p_param)) { + return param_cache[p_param]; + } else { + return Variant(); + } } void ShaderMaterial::_shader_changed() { @@ -243,7 +269,7 @@ void ShaderMaterial::_bind_methods() { void ShaderMaterial::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { #ifdef TOOLS_ENABLED - const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", 0) ? "'" : "\""; + const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\""; #else const String quote_style = "\""; #endif @@ -253,8 +279,8 @@ void ShaderMaterial::get_argument_options(const StringName &p_function, int p_id if (shader.is_valid()) { List<PropertyInfo> pl; shader->get_param_list(&pl); - for (List<PropertyInfo>::Element *E = pl.front(); E; E = E->next()) { - r_options->push_back(quote_style + E->get().name.replace_first("shader_param/", "") + quote_style); + for (const PropertyInfo &E : pl) { + r_options->push_back(E.name.replace_first("shader_param/", "").quote(quote_style)); } } } @@ -444,7 +470,12 @@ void BaseMaterial3D::_update_shader() { //must create a shader! - String code = "shader_type spatial;\nrender_mode "; + // Add a comment to describe the shader origin (useful when converting to ShaderMaterial). + String code = vformat( + "// NOTE: Shader automatically converted from " VERSION_NAME " " VERSION_FULL_CONFIG "'s %s.\n\n", + orm ? "ORMMaterial3D" : "StandardMaterial3D"); + + code += "shader_type spatial;\nrender_mode "; switch (blend_mode) { case BLEND_MODE_MIX: code += "blend_mix"; @@ -732,148 +763,148 @@ void BaseMaterial3D::_update_shader() { code += "void vertex() {\n"; if (flags[FLAG_SRGB_VERTEX_COLOR]) { - code += "\tif (!OUTPUT_IS_SRGB) {\n"; - code += "\t\tCOLOR.rgb = mix(pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb * (1.0 / 12.92), lessThan(COLOR.rgb, vec3(0.04045)));\n"; - code += "\t}\n"; + code += " if (!OUTPUT_IS_SRGB) {\n"; + code += " COLOR.rgb = mix(pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb * (1.0 / 12.92), lessThan(COLOR.rgb, vec3(0.04045)));\n"; + code += " }\n"; } if (flags[FLAG_USE_POINT_SIZE]) { - code += "\tPOINT_SIZE=point_size;\n"; + code += " POINT_SIZE=point_size;\n"; } if (shading_mode == SHADING_MODE_PER_VERTEX) { - code += "\tROUGHNESS=roughness;\n"; + code += " ROUGHNESS=roughness;\n"; } if (!flags[FLAG_UV1_USE_TRIPLANAR]) { - code += "\tUV=UV*uv1_scale.xy+uv1_offset.xy;\n"; + code += " UV=UV*uv1_scale.xy+uv1_offset.xy;\n"; } switch (billboard_mode) { case BILLBOARD_DISABLED: { } break; case BILLBOARD_ENABLED: { - code += "\tMODELVIEW_MATRIX = INV_CAMERA_MATRIX * mat4(CAMERA_MATRIX[0],CAMERA_MATRIX[1],CAMERA_MATRIX[2],WORLD_MATRIX[3]);\n"; + code += " MODELVIEW_MATRIX = INV_CAMERA_MATRIX * mat4(CAMERA_MATRIX[0],CAMERA_MATRIX[1],CAMERA_MATRIX[2],WORLD_MATRIX[3]);\n"; if (flags[FLAG_BILLBOARD_KEEP_SCALE]) { - code += "\tMODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(vec4(length(WORLD_MATRIX[0].xyz), 0.0, 0.0, 0.0),vec4(0.0, length(WORLD_MATRIX[1].xyz), 0.0, 0.0),vec4(0.0, 0.0, length(WORLD_MATRIX[2].xyz), 0.0),vec4(0.0, 0.0, 0.0, 1.0));\n"; + code += " MODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(vec4(length(WORLD_MATRIX[0].xyz), 0.0, 0.0, 0.0),vec4(0.0, length(WORLD_MATRIX[1].xyz), 0.0, 0.0),vec4(0.0, 0.0, length(WORLD_MATRIX[2].xyz), 0.0),vec4(0.0, 0.0, 0.0, 1.0));\n"; } } break; case BILLBOARD_FIXED_Y: { - code += "\tMODELVIEW_MATRIX = INV_CAMERA_MATRIX * mat4(CAMERA_MATRIX[0],WORLD_MATRIX[1],vec4(normalize(cross(CAMERA_MATRIX[0].xyz,WORLD_MATRIX[1].xyz)), 0.0),WORLD_MATRIX[3]);\n"; + code += " MODELVIEW_MATRIX = INV_CAMERA_MATRIX * mat4(CAMERA_MATRIX[0],WORLD_MATRIX[1],vec4(normalize(cross(CAMERA_MATRIX[0].xyz,WORLD_MATRIX[1].xyz)), 0.0),WORLD_MATRIX[3]);\n"; if (flags[FLAG_BILLBOARD_KEEP_SCALE]) { - code += "\tMODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(vec4(length(WORLD_MATRIX[0].xyz), 0.0, 0.0, 0.0),vec4(0.0, 1.0, 0.0, 0.0),vec4(0.0, 0.0, length(WORLD_MATRIX[2].xyz), 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; + code += " MODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(vec4(length(WORLD_MATRIX[0].xyz), 0.0, 0.0, 0.0),vec4(0.0, 1.0, 0.0, 0.0),vec4(0.0, 0.0, length(WORLD_MATRIX[2].xyz), 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; } else { - code += "\tMODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(vec4(1.0, 0.0, 0.0, 0.0),vec4(0.0, 1.0/length(WORLD_MATRIX[1].xyz), 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0),vec4(0.0, 0.0, 0.0 ,1.0));\n"; + code += " MODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(vec4(1.0, 0.0, 0.0, 0.0),vec4(0.0, 1.0/length(WORLD_MATRIX[1].xyz), 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0),vec4(0.0, 0.0, 0.0 ,1.0));\n"; } } break; case BILLBOARD_PARTICLES: { //make billboard - code += "\tmat4 mat_world = mat4(normalize(CAMERA_MATRIX[0])*length(WORLD_MATRIX[0]),normalize(CAMERA_MATRIX[1])*length(WORLD_MATRIX[0]),normalize(CAMERA_MATRIX[2])*length(WORLD_MATRIX[2]),WORLD_MATRIX[3]);\n"; + code += " mat4 mat_world = mat4(normalize(CAMERA_MATRIX[0])*length(WORLD_MATRIX[0]),normalize(CAMERA_MATRIX[1])*length(WORLD_MATRIX[0]),normalize(CAMERA_MATRIX[2])*length(WORLD_MATRIX[2]),WORLD_MATRIX[3]);\n"; //rotate by rotation - code += "\tmat_world = mat_world * mat4( vec4(cos(INSTANCE_CUSTOM.x),-sin(INSTANCE_CUSTOM.x), 0.0, 0.0), vec4(sin(INSTANCE_CUSTOM.x), cos(INSTANCE_CUSTOM.x), 0.0, 0.0),vec4(0.0, 0.0, 1.0, 0.0),vec4(0.0, 0.0, 0.0, 1.0));\n"; + code += " mat_world = mat_world * mat4( vec4(cos(INSTANCE_CUSTOM.x),-sin(INSTANCE_CUSTOM.x), 0.0, 0.0), vec4(sin(INSTANCE_CUSTOM.x), cos(INSTANCE_CUSTOM.x), 0.0, 0.0),vec4(0.0, 0.0, 1.0, 0.0),vec4(0.0, 0.0, 0.0, 1.0));\n"; //set modelview - code += "\tMODELVIEW_MATRIX = INV_CAMERA_MATRIX * mat_world;\n"; + code += " MODELVIEW_MATRIX = INV_CAMERA_MATRIX * mat_world;\n"; //handle animation - code += "\tfloat h_frames = float(particles_anim_h_frames);\n"; - code += "\tfloat v_frames = float(particles_anim_v_frames);\n"; - code += "\tfloat particle_total_frames = float(particles_anim_h_frames * particles_anim_v_frames);\n"; - code += "\tfloat particle_frame = floor(INSTANCE_CUSTOM.z * float(particle_total_frames));\n"; - code += "\tif (!particles_anim_loop) {\n"; - code += "\t\tparticle_frame = clamp(particle_frame, 0.0, particle_total_frames - 1.0);\n"; - code += "\t} else {\n"; - code += "\t\tparticle_frame = mod(particle_frame, particle_total_frames);\n"; - code += "\t}"; - code += "\tUV /= vec2(h_frames, v_frames);\n"; - code += "\tUV += vec2(mod(particle_frame, h_frames) / h_frames, floor(particle_frame / h_frames) / v_frames);\n"; + code += " float h_frames = float(particles_anim_h_frames);\n"; + code += " float v_frames = float(particles_anim_v_frames);\n"; + code += " float particle_total_frames = float(particles_anim_h_frames * particles_anim_v_frames);\n"; + code += " float particle_frame = floor(INSTANCE_CUSTOM.z * float(particle_total_frames));\n"; + code += " if (!particles_anim_loop) {\n"; + code += " particle_frame = clamp(particle_frame, 0.0, particle_total_frames - 1.0);\n"; + code += " } else {\n"; + code += " particle_frame = mod(particle_frame, particle_total_frames);\n"; + code += " }"; + code += " UV /= vec2(h_frames, v_frames);\n"; + code += " UV += vec2(mod(particle_frame, h_frames) / h_frames, floor(particle_frame / h_frames) / v_frames);\n"; } break; case BILLBOARD_MAX: break; // Internal value, skip. } if (flags[FLAG_FIXED_SIZE]) { - code += "\tif (PROJECTION_MATRIX[3][3] != 0.0) {\n"; + code += " if (PROJECTION_MATRIX[3][3] != 0.0) {\n"; //orthogonal matrix, try to do about the same //with viewport size - code += "\t\tfloat h = abs(1.0 / (2.0 * PROJECTION_MATRIX[1][1]));\n"; - code += "\t\tfloat sc = (h * 2.0); //consistent with Y-fov\n"; - code += "\t\tMODELVIEW_MATRIX[0]*=sc;\n"; - code += "\t\tMODELVIEW_MATRIX[1]*=sc;\n"; - code += "\t\tMODELVIEW_MATRIX[2]*=sc;\n"; - code += "\t} else {\n"; + code += " float h = abs(1.0 / (2.0 * PROJECTION_MATRIX[1][1]));\n"; + code += " float sc = (h * 2.0); //consistent with Y-fov\n"; + code += " MODELVIEW_MATRIX[0]*=sc;\n"; + code += " MODELVIEW_MATRIX[1]*=sc;\n"; + code += " MODELVIEW_MATRIX[2]*=sc;\n"; + code += " } else {\n"; //just scale by depth - code += "\t\tfloat sc = -(MODELVIEW_MATRIX)[3].z;\n"; - code += "\t\tMODELVIEW_MATRIX[0]*=sc;\n"; - code += "\t\tMODELVIEW_MATRIX[1]*=sc;\n"; - code += "\t\tMODELVIEW_MATRIX[2]*=sc;\n"; - code += "\t}\n"; + code += " float sc = -(MODELVIEW_MATRIX)[3].z;\n"; + code += " MODELVIEW_MATRIX[0]*=sc;\n"; + code += " MODELVIEW_MATRIX[1]*=sc;\n"; + code += " MODELVIEW_MATRIX[2]*=sc;\n"; + code += " }\n"; } if (detail_uv == DETAIL_UV_2 && !flags[FLAG_UV2_USE_TRIPLANAR]) { - code += "\tUV2=UV2*uv2_scale.xy+uv2_offset.xy;\n"; + code += " UV2=UV2*uv2_scale.xy+uv2_offset.xy;\n"; } if (flags[FLAG_UV1_USE_TRIPLANAR] || flags[FLAG_UV2_USE_TRIPLANAR]) { //generate tangent and binormal in world space - code += "\tTANGENT = vec3(0.0,0.0,-1.0) * abs(NORMAL.x);\n"; - code += "\tTANGENT+= vec3(1.0,0.0,0.0) * abs(NORMAL.y);\n"; - code += "\tTANGENT+= vec3(1.0,0.0,0.0) * abs(NORMAL.z);\n"; - code += "\tTANGENT = normalize(TANGENT);\n"; + code += " TANGENT = vec3(0.0,0.0,-1.0) * abs(NORMAL.x);\n"; + code += " TANGENT+= vec3(1.0,0.0,0.0) * abs(NORMAL.y);\n"; + code += " TANGENT+= vec3(1.0,0.0,0.0) * abs(NORMAL.z);\n"; + code += " TANGENT = normalize(TANGENT);\n"; - code += "\tBINORMAL = vec3(0.0,1.0,0.0) * abs(NORMAL.x);\n"; - code += "\tBINORMAL+= vec3(0.0,0.0,-1.0) * abs(NORMAL.y);\n"; - code += "\tBINORMAL+= vec3(0.0,1.0,0.0) * abs(NORMAL.z);\n"; - code += "\tBINORMAL = normalize(BINORMAL);\n"; + code += " BINORMAL = vec3(0.0,1.0,0.0) * abs(NORMAL.x);\n"; + code += " BINORMAL+= vec3(0.0,0.0,-1.0) * abs(NORMAL.y);\n"; + code += " BINORMAL+= vec3(0.0,1.0,0.0) * abs(NORMAL.z);\n"; + code += " BINORMAL = normalize(BINORMAL);\n"; } if (flags[FLAG_UV1_USE_TRIPLANAR]) { if (flags[FLAG_UV1_USE_WORLD_TRIPLANAR]) { - code += "\tuv1_power_normal=pow(abs(mat3(WORLD_MATRIX) * NORMAL),vec3(uv1_blend_sharpness));\n"; - code += "\tuv1_triplanar_pos = (WORLD_MATRIX * vec4(VERTEX, 1.0f)).xyz * uv1_scale + uv1_offset;\n"; + code += " uv1_power_normal=pow(abs(mat3(WORLD_MATRIX) * NORMAL),vec3(uv1_blend_sharpness));\n"; + code += " uv1_triplanar_pos = (WORLD_MATRIX * vec4(VERTEX, 1.0f)).xyz * uv1_scale + uv1_offset;\n"; } else { - code += "\tuv1_power_normal=pow(abs(NORMAL),vec3(uv1_blend_sharpness));\n"; - code += "\tuv1_triplanar_pos = VERTEX * uv1_scale + uv1_offset;\n"; + code += " uv1_power_normal=pow(abs(NORMAL),vec3(uv1_blend_sharpness));\n"; + code += " uv1_triplanar_pos = VERTEX * uv1_scale + uv1_offset;\n"; } - code += "\tuv1_power_normal/=dot(uv1_power_normal,vec3(1.0));\n"; - code += "\tuv1_triplanar_pos *= vec3(1.0,-1.0, 1.0);\n"; + code += " uv1_power_normal/=dot(uv1_power_normal,vec3(1.0));\n"; + code += " uv1_triplanar_pos *= vec3(1.0,-1.0, 1.0);\n"; } if (flags[FLAG_UV2_USE_TRIPLANAR]) { if (flags[FLAG_UV2_USE_WORLD_TRIPLANAR]) { - code += "\tuv2_power_normal=pow(abs(mat3(WORLD_MATRIX) * NORMAL), vec3(uv2_blend_sharpness));\n"; - code += "\tuv2_triplanar_pos = (WORLD_MATRIX * vec4(VERTEX, 1.0f)).xyz * uv2_scale + uv2_offset;\n"; + code += " uv2_power_normal=pow(abs(mat3(WORLD_MATRIX) * NORMAL), vec3(uv2_blend_sharpness));\n"; + code += " uv2_triplanar_pos = (WORLD_MATRIX * vec4(VERTEX, 1.0f)).xyz * uv2_scale + uv2_offset;\n"; } else { - code += "\tuv2_power_normal=pow(abs(NORMAL), vec3(uv2_blend_sharpness));\n"; - code += "\tuv2_triplanar_pos = VERTEX * uv2_scale + uv2_offset;\n"; + code += " uv2_power_normal=pow(abs(NORMAL), vec3(uv2_blend_sharpness));\n"; + code += " uv2_triplanar_pos = VERTEX * uv2_scale + uv2_offset;\n"; } - code += "\tuv2_power_normal/=dot(uv2_power_normal,vec3(1.0));\n"; - code += "\tuv2_triplanar_pos *= vec3(1.0,-1.0, 1.0);\n"; + code += " uv2_power_normal/=dot(uv2_power_normal,vec3(1.0));\n"; + code += " uv2_triplanar_pos *= vec3(1.0,-1.0, 1.0);\n"; } if (grow_enabled) { - code += "\tVERTEX+=NORMAL*grow;\n"; + code += " VERTEX+=NORMAL*grow;\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 += "\tvec4 samp=vec4(0.0);\n"; - code += "\tsamp+= texture(p_sampler,p_triplanar_pos.xy) * p_weights.z;\n"; - code += "\tsamp+= texture(p_sampler,p_triplanar_pos.xz) * p_weights.y;\n"; - code += "\tsamp+= texture(p_sampler,p_triplanar_pos.zy * vec2(-1.0,1.0)) * p_weights.x;\n"; - code += "\treturn samp;\n"; + code += " vec4 samp=vec4(0.0);\n"; + code += " samp+= texture(p_sampler,p_triplanar_pos.xy) * p_weights.z;\n"; + code += " samp+= texture(p_sampler,p_triplanar_pos.xz) * p_weights.y;\n"; + code += " samp+= texture(p_sampler,p_triplanar_pos.zy * vec2(-1.0,1.0)) * p_weights.x;\n"; + code += " return samp;\n"; code += "}\n"; } code += "\n\n"; code += "void fragment() {\n"; if (!flags[FLAG_UV1_USE_TRIPLANAR]) { - code += "\tvec2 base_uv = UV;\n"; + code += " vec2 base_uv = UV;\n"; } if ((features[FEATURE_DETAIL] && detail_uv == DETAIL_UV_2) || (features[FEATURE_AMBIENT_OCCLUSION] && flags[FLAG_AO_ON_UV2]) || (features[FEATURE_EMISSION] && flags[FLAG_EMISSION_ON_UV2])) { - code += "\tvec2 base_uv2 = UV2;\n"; + code += " vec2 base_uv2 = UV2;\n"; } if (features[FEATURE_HEIGHT_MAPPING] && flags[FLAG_UV1_USE_TRIPLANAR]) { @@ -891,317 +922,319 @@ void BaseMaterial3D::_update_shader() { } if (!RenderingServer::get_singleton()->is_low_end() && features[FEATURE_HEIGHT_MAPPING] && !flags[FLAG_UV1_USE_TRIPLANAR]) { //heightmap not supported with triplanar - code += "\t{\n"; - code += "\t\tvec3 view_dir = normalize(normalize(-VERTEX)*mat3(TANGENT*heightmap_flip.x,-BINORMAL*heightmap_flip.y,NORMAL));\n"; // binormal is negative due to mikktspace, flip 'unflips' it ;-) + code += " {\n"; + code += " vec3 view_dir = normalize(normalize(-VERTEX)*mat3(TANGENT*heightmap_flip.x,-BINORMAL*heightmap_flip.y,NORMAL));\n"; // binormal is negative due to mikktspace, flip 'unflips' it ;-) if (deep_parallax) { - code += "\t\tfloat num_layers = mix(float(heightmap_max_layers),float(heightmap_min_layers), abs(dot(vec3(0.0, 0.0, 1.0), view_dir)));\n"; - code += "\t\tfloat layer_depth = 1.0 / num_layers;\n"; - code += "\t\tfloat current_layer_depth = 0.0;\n"; - code += "\t\tvec2 P = view_dir.xy * heightmap_scale;\n"; - code += "\t\tvec2 delta = P / num_layers;\n"; - code += "\t\tvec2 ofs = base_uv;\n"; + code += " float num_layers = mix(float(heightmap_max_layers),float(heightmap_min_layers), abs(dot(vec3(0.0, 0.0, 1.0), view_dir)));\n"; + code += " float layer_depth = 1.0 / num_layers;\n"; + code += " float current_layer_depth = 0.0;\n"; + code += " vec2 P = view_dir.xy * heightmap_scale;\n"; + code += " vec2 delta = P / num_layers;\n"; + code += " vec2 ofs = base_uv;\n"; if (flags[FLAG_INVERT_HEIGHTMAP]) { - code += "\t\tfloat depth = texture(texture_heightmap, ofs).r;\n"; + code += " float depth = texture(texture_heightmap, ofs).r;\n"; } else { - code += "\t\tfloat depth = 1.0 - texture(texture_heightmap, ofs).r;\n"; + code += " float depth = 1.0 - texture(texture_heightmap, ofs).r;\n"; } - code += "\t\tfloat current_depth = 0.0;\n"; - code += "\t\twhile(current_depth < depth) {\n"; - code += "\t\t\tofs -= delta;\n"; + code += " float current_depth = 0.0;\n"; + code += " while(current_depth < depth) {\n"; + code += " ofs -= delta;\n"; if (flags[FLAG_INVERT_HEIGHTMAP]) { - code += "\t\t\tdepth = texture(texture_heightmap, ofs).r;\n"; + code += " depth = texture(texture_heightmap, ofs).r;\n"; } else { - code += "\t\t\tdepth = 1.0 - texture(texture_heightmap, ofs).r;\n"; + code += " depth = 1.0 - texture(texture_heightmap, ofs).r;\n"; } - code += "\t\t\tcurrent_depth += layer_depth;\n"; - code += "\t\t}\n"; - code += "\t\tvec2 prev_ofs = ofs + delta;\n"; - code += "\t\tfloat after_depth = depth - current_depth;\n"; + code += " current_depth += layer_depth;\n"; + code += " }\n"; + code += " vec2 prev_ofs = ofs + delta;\n"; + code += " float after_depth = depth - current_depth;\n"; if (flags[FLAG_INVERT_HEIGHTMAP]) { - code += "\t\tfloat before_depth = texture(texture_heightmap, prev_ofs).r - current_depth + layer_depth;\n"; + code += " float before_depth = texture(texture_heightmap, prev_ofs).r - current_depth + layer_depth;\n"; } else { - code += "\t\tfloat before_depth = ( 1.0 - texture(texture_heightmap, prev_ofs).r ) - current_depth + layer_depth;\n"; + code += " float before_depth = ( 1.0 - texture(texture_heightmap, prev_ofs).r ) - current_depth + layer_depth;\n"; } - code += "\t\tfloat weight = after_depth / (after_depth - before_depth);\n"; - code += "\t\tofs = mix(ofs,prev_ofs,weight);\n"; + code += " float weight = after_depth / (after_depth - before_depth);\n"; + code += " ofs = mix(ofs,prev_ofs,weight);\n"; } else { if (flags[FLAG_INVERT_HEIGHTMAP]) { - code += "\t\tfloat depth = texture(texture_heightmap, base_uv).r;\n"; + code += " float depth = texture(texture_heightmap, base_uv).r;\n"; } else { - code += "\t\tfloat depth = 1.0 - texture(texture_heightmap, base_uv).r;\n"; + code += " float depth = 1.0 - texture(texture_heightmap, base_uv).r;\n"; } - code += "\t\tvec2 ofs = base_uv - view_dir.xy / view_dir.z * (depth * heightmap_scale);\n"; + // Use offset limiting to improve the appearance of non-deep parallax. + // This reduces the impression of depth, but avoids visible warping in the distance. + code += " vec2 ofs = base_uv - view_dir.xy * depth * heightmap_scale;\n"; } - code += "\t\tbase_uv=ofs;\n"; + code += " base_uv=ofs;\n"; if (features[FEATURE_DETAIL] && detail_uv == DETAIL_UV_2) { - code += "\t\tbase_uv2-=ofs;\n"; + code += " base_uv2-=ofs;\n"; } - code += "\t}\n"; + code += " }\n"; } if (flags[FLAG_USE_POINT_SIZE]) { - code += "\tvec4 albedo_tex = texture(texture_albedo,POINT_COORD);\n"; + code += " vec4 albedo_tex = texture(texture_albedo,POINT_COORD);\n"; } else { if (flags[FLAG_UV1_USE_TRIPLANAR]) { - code += "\tvec4 albedo_tex = triplanar_texture(texture_albedo,uv1_power_normal,uv1_triplanar_pos);\n"; + code += " vec4 albedo_tex = triplanar_texture(texture_albedo,uv1_power_normal,uv1_triplanar_pos);\n"; } else { - code += "\tvec4 albedo_tex = texture(texture_albedo,base_uv);\n"; + code += " vec4 albedo_tex = texture(texture_albedo,base_uv);\n"; } } if (flags[FLAG_ALBEDO_TEXTURE_FORCE_SRGB]) { - code += "\talbedo_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"; + 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"; } if (flags[FLAG_ALBEDO_FROM_VERTEX_COLOR]) { - code += "\talbedo_tex *= COLOR;\n"; + code += " albedo_tex *= COLOR;\n"; } - code += "\tALBEDO = albedo.rgb * albedo_tex.rgb;\n"; + code += " ALBEDO = albedo.rgb * albedo_tex.rgb;\n"; if (!orm) { if (flags[FLAG_UV1_USE_TRIPLANAR]) { - code += "\tfloat metallic_tex = dot(triplanar_texture(texture_metallic,uv1_power_normal,uv1_triplanar_pos),metallic_texture_channel);\n"; + code += " float metallic_tex = dot(triplanar_texture(texture_metallic,uv1_power_normal,uv1_triplanar_pos),metallic_texture_channel);\n"; } else { - code += "\tfloat metallic_tex = dot(texture(texture_metallic,base_uv),metallic_texture_channel);\n"; + code += " float metallic_tex = dot(texture(texture_metallic,base_uv),metallic_texture_channel);\n"; } - code += "\tMETALLIC = metallic_tex * metallic;\n"; + code += " METALLIC = metallic_tex * metallic;\n"; switch (roughness_texture_channel) { case TEXTURE_CHANNEL_RED: { - code += "\tvec4 roughness_texture_channel = vec4(1.0,0.0,0.0,0.0);\n"; + code += " vec4 roughness_texture_channel = vec4(1.0,0.0,0.0,0.0);\n"; } break; case TEXTURE_CHANNEL_GREEN: { - code += "\tvec4 roughness_texture_channel = vec4(0.0,1.0,0.0,0.0);\n"; + code += " vec4 roughness_texture_channel = vec4(0.0,1.0,0.0,0.0);\n"; } break; case TEXTURE_CHANNEL_BLUE: { - code += "\tvec4 roughness_texture_channel = vec4(0.0,0.0,1.0,0.0);\n"; + code += " vec4 roughness_texture_channel = vec4(0.0,0.0,1.0,0.0);\n"; } break; case TEXTURE_CHANNEL_ALPHA: { - code += "\tvec4 roughness_texture_channel = vec4(0.0,0.0,0.0,1.0);\n"; + code += " vec4 roughness_texture_channel = vec4(0.0,0.0,0.0,1.0);\n"; } break; case TEXTURE_CHANNEL_GRAYSCALE: { - code += "\tvec4 roughness_texture_channel = vec4(0.333333,0.333333,0.333333,0.0);\n"; + code += " vec4 roughness_texture_channel = vec4(0.333333,0.333333,0.333333,0.0);\n"; } break; case TEXTURE_CHANNEL_MAX: break; // Internal value, skip. } if (flags[FLAG_UV1_USE_TRIPLANAR]) { - code += "\tfloat roughness_tex = dot(triplanar_texture(texture_roughness,uv1_power_normal,uv1_triplanar_pos),roughness_texture_channel);\n"; + code += " float roughness_tex = dot(triplanar_texture(texture_roughness,uv1_power_normal,uv1_triplanar_pos),roughness_texture_channel);\n"; } else { - code += "\tfloat roughness_tex = dot(texture(texture_roughness,base_uv),roughness_texture_channel);\n"; + code += " float roughness_tex = dot(texture(texture_roughness,base_uv),roughness_texture_channel);\n"; } - code += "\tROUGHNESS = roughness_tex * roughness;\n"; - code += "\tSPECULAR = specular;\n"; + code += " ROUGHNESS = roughness_tex * roughness;\n"; + code += " SPECULAR = specular;\n"; } else { if (flags[FLAG_UV1_USE_TRIPLANAR]) { - code += "\tfloat orm_tex = triplanar_texture(texture_orm,uv1_power_normal,uv1_triplanar_pos);\n"; + code += " vec4 orm_tex = triplanar_texture(texture_orm,uv1_power_normal,uv1_triplanar_pos);\n"; } else { - code += "\tfloat orm_tex = texture(texture_orm,base_uv);\n"; + code += " vec4 orm_tex = texture(texture_orm,base_uv);\n"; } - code += "\tROUGHNESS = orm_tex.g;\n"; - code += "\tMETALLIC = orm_tex.b;\n"; + code += " ROUGHNESS = orm_tex.g;\n"; + code += " METALLIC = orm_tex.b;\n"; } if (features[FEATURE_NORMAL_MAPPING]) { if (flags[FLAG_UV1_USE_TRIPLANAR]) { - code += "\tNORMAL_MAP = triplanar_texture(texture_normal,uv1_power_normal,uv1_triplanar_pos).rgb;\n"; + code += " NORMAL_MAP = triplanar_texture(texture_normal,uv1_power_normal,uv1_triplanar_pos).rgb;\n"; } else { - code += "\tNORMAL_MAP = texture(texture_normal,base_uv).rgb;\n"; + code += " NORMAL_MAP = texture(texture_normal,base_uv).rgb;\n"; } - code += "\tNORMAL_MAP_DEPTH = normal_scale;\n"; + code += " NORMAL_MAP_DEPTH = normal_scale;\n"; } if (features[FEATURE_EMISSION]) { if (flags[FLAG_EMISSION_ON_UV2]) { if (flags[FLAG_UV2_USE_TRIPLANAR]) { - code += "\tvec3 emission_tex = triplanar_texture(texture_emission,uv2_power_normal,uv2_triplanar_pos).rgb;\n"; + code += " vec3 emission_tex = triplanar_texture(texture_emission,uv2_power_normal,uv2_triplanar_pos).rgb;\n"; } else { - code += "\tvec3 emission_tex = texture(texture_emission,base_uv2).rgb;\n"; + code += " vec3 emission_tex = texture(texture_emission,base_uv2).rgb;\n"; } } else { if (flags[FLAG_UV1_USE_TRIPLANAR]) { - code += "\tvec3 emission_tex = triplanar_texture(texture_emission,uv1_power_normal,uv1_triplanar_pos).rgb;\n"; + code += " vec3 emission_tex = triplanar_texture(texture_emission,uv1_power_normal,uv1_triplanar_pos).rgb;\n"; } else { - code += "\tvec3 emission_tex = texture(texture_emission,base_uv).rgb;\n"; + code += " vec3 emission_tex = texture(texture_emission,base_uv).rgb;\n"; } } if (emission_op == EMISSION_OP_ADD) { - code += "\tEMISSION = (emission.rgb+emission_tex)*emission_energy;\n"; + code += " EMISSION = (emission.rgb+emission_tex)*emission_energy;\n"; } else { - code += "\tEMISSION = (emission.rgb*emission_tex)*emission_energy;\n"; + code += " EMISSION = (emission.rgb*emission_tex)*emission_energy;\n"; } } if (features[FEATURE_REFRACTION]) { if (features[FEATURE_NORMAL_MAPPING]) { - code += "\tvec3 unpacked_normal = NORMAL_MAP;\n"; - code += "\tunpacked_normal.xy = unpacked_normal.xy * 2.0 - 1.0;\n"; - code += "\tunpacked_normal.z = sqrt(max(0.0, 1.0 - dot(unpacked_normal.xy, unpacked_normal.xy)));\n"; - code += "\tvec3 ref_normal = normalize( mix(NORMAL,TANGENT * unpacked_normal.x + BINORMAL * unpacked_normal.y + NORMAL * unpacked_normal.z,NORMAL_MAP_DEPTH) );\n"; + code += " vec3 unpacked_normal = NORMAL_MAP;\n"; + code += " unpacked_normal.xy = unpacked_normal.xy * 2.0 - 1.0;\n"; + code += " unpacked_normal.z = sqrt(max(0.0, 1.0 - dot(unpacked_normal.xy, unpacked_normal.xy)));\n"; + code += " vec3 ref_normal = normalize( mix(NORMAL,TANGENT * unpacked_normal.x + BINORMAL * unpacked_normal.y + NORMAL * unpacked_normal.z,NORMAL_MAP_DEPTH) );\n"; } else { - code += "\tvec3 ref_normal = NORMAL;\n"; + code += " vec3 ref_normal = NORMAL;\n"; } if (flags[FLAG_UV1_USE_TRIPLANAR]) { - code += "\tvec2 ref_ofs = SCREEN_UV - ref_normal.xy * dot(triplanar_texture(texture_refraction,uv1_power_normal,uv1_triplanar_pos),refraction_texture_channel) * refraction;\n"; + code += " vec2 ref_ofs = SCREEN_UV - ref_normal.xy * dot(triplanar_texture(texture_refraction,uv1_power_normal,uv1_triplanar_pos),refraction_texture_channel) * refraction;\n"; } else { - code += "\tvec2 ref_ofs = SCREEN_UV - ref_normal.xy * dot(texture(texture_refraction,base_uv),refraction_texture_channel) * refraction;\n"; + code += " vec2 ref_ofs = SCREEN_UV - ref_normal.xy * dot(texture(texture_refraction,base_uv),refraction_texture_channel) * refraction;\n"; } - code += "\tfloat ref_amount = 1.0 - albedo.a * albedo_tex.a;\n"; - code += "\tEMISSION += textureLod(SCREEN_TEXTURE,ref_ofs,ROUGHNESS * 8.0).rgb * ref_amount;\n"; - code += "\tALBEDO *= 1.0 - ref_amount;\n"; - code += "\tALPHA = 1.0;\n"; + code += " float ref_amount = 1.0 - albedo.a * albedo_tex.a;\n"; + code += " EMISSION += textureLod(SCREEN_TEXTURE,ref_ofs,ROUGHNESS * 8.0).rgb * ref_amount;\n"; + code += " ALBEDO *= 1.0 - ref_amount;\n"; + code += " ALPHA = 1.0;\n"; } else if (transparency != TRANSPARENCY_DISABLED || flags[FLAG_USE_SHADOW_TO_OPACITY] || (distance_fade == DISTANCE_FADE_PIXEL_ALPHA) || proximity_fade_enabled) { - code += "\tALPHA = albedo.a * albedo_tex.a;\n"; + code += " ALPHA = albedo.a * albedo_tex.a;\n"; } if (transparency == TRANSPARENCY_ALPHA_HASH) { - code += "\tALPHA_HASH_SCALE = alpha_hash_scale;\n"; + code += " ALPHA_HASH_SCALE = alpha_hash_scale;\n"; } else if (transparency == TRANSPARENCY_ALPHA_SCISSOR) { - code += "\tALPHA_SCISSOR_THRESHOLD = alpha_scissor_threshold;\n"; + code += " ALPHA_SCISSOR_THRESHOLD = alpha_scissor_threshold;\n"; } if (alpha_antialiasing_mode != ALPHA_ANTIALIASING_OFF && (transparency == TRANSPARENCY_ALPHA_HASH || transparency == TRANSPARENCY_ALPHA_SCISSOR)) { - code += "\tALPHA_ANTIALIASING_EDGE = alpha_antialiasing_edge;\n"; - code += "\tALPHA_TEXTURE_COORDINATE = UV * vec2(albedo_texture_size);\n"; + code += " ALPHA_ANTIALIASING_EDGE = alpha_antialiasing_edge;\n"; + code += " ALPHA_TEXTURE_COORDINATE = UV * vec2(albedo_texture_size);\n"; } if (proximity_fade_enabled) { - code += "\tfloat depth_tex = textureLod(DEPTH_TEXTURE,SCREEN_UV,0.0).r;\n"; - code += "\tvec4 world_pos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV*2.0-1.0,depth_tex*2.0-1.0,1.0);\n"; - code += "\tworld_pos.xyz/=world_pos.w;\n"; - code += "\tALPHA*=clamp(1.0-smoothstep(world_pos.z+proximity_fade_distance,world_pos.z,VERTEX.z),0.0,1.0);\n"; + code += " float depth_tex = textureLod(DEPTH_TEXTURE,SCREEN_UV,0.0).r;\n"; + code += " vec4 world_pos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV*2.0-1.0,depth_tex*2.0-1.0,1.0);\n"; + code += " world_pos.xyz/=world_pos.w;\n"; + code += " ALPHA*=clamp(1.0-smoothstep(world_pos.z+proximity_fade_distance,world_pos.z,VERTEX.z),0.0,1.0);\n"; } if (distance_fade != DISTANCE_FADE_DISABLED) { if ((distance_fade == DISTANCE_FADE_OBJECT_DITHER || distance_fade == DISTANCE_FADE_PIXEL_DITHER)) { if (!RenderingServer::get_singleton()->is_low_end()) { - code += "\t{\n"; + code += " {\n"; if (distance_fade == DISTANCE_FADE_OBJECT_DITHER) { - code += "\t\tfloat fade_distance = abs((INV_CAMERA_MATRIX * WORLD_MATRIX[3]).z);\n"; + code += " float fade_distance = abs((INV_CAMERA_MATRIX * WORLD_MATRIX[3]).z);\n"; } else { - code += "\t\tfloat fade_distance=-VERTEX.z;\n"; + code += " float fade_distance=-VERTEX.z;\n"; } - code += "\t\tfloat fade=clamp(smoothstep(distance_fade_min,distance_fade_max,fade_distance),0.0,1.0);\n"; - code += "\t\tint x = int(FRAGCOORD.x) % 4;\n"; - code += "\t\tint y = int(FRAGCOORD.y) % 4;\n"; - code += "\t\tint index = x + y * 4;\n"; - code += "\t\tfloat limit = 0.0;\n\n"; - code += "\t\tif (x < 8) {\n"; - code += "\t\t\tif (index == 0) limit = 0.0625;\n"; - code += "\t\t\tif (index == 1) limit = 0.5625;\n"; - code += "\t\t\tif (index == 2) limit = 0.1875;\n"; - code += "\t\t\tif (index == 3) limit = 0.6875;\n"; - code += "\t\t\tif (index == 4) limit = 0.8125;\n"; - code += "\t\t\tif (index == 5) limit = 0.3125;\n"; - code += "\t\t\tif (index == 6) limit = 0.9375;\n"; - code += "\t\t\tif (index == 7) limit = 0.4375;\n"; - code += "\t\t\tif (index == 8) limit = 0.25;\n"; - code += "\t\t\tif (index == 9) limit = 0.75;\n"; - code += "\t\t\tif (index == 10) limit = 0.125;\n"; - code += "\t\t\tif (index == 11) limit = 0.625;\n"; - code += "\t\t\tif (index == 12) limit = 1.0;\n"; - code += "\t\t\tif (index == 13) limit = 0.5;\n"; - code += "\t\t\tif (index == 14) limit = 0.875;\n"; - code += "\t\t\tif (index == 15) limit = 0.375;\n"; - code += "\t\t}\n\n"; - code += "\tif (fade < limit)\n"; - code += "\t\tdiscard;\n"; - code += "\t}\n\n"; + code += " float fade=clamp(smoothstep(distance_fade_min,distance_fade_max,fade_distance),0.0,1.0);\n"; + code += " int x = int(FRAGCOORD.x) % 4;\n"; + code += " int y = int(FRAGCOORD.y) % 4;\n"; + code += " int index = x + y * 4;\n"; + code += " float limit = 0.0;\n\n"; + code += " if (x < 8) {\n"; + code += " if (index == 0) limit = 0.0625;\n"; + code += " if (index == 1) limit = 0.5625;\n"; + code += " if (index == 2) limit = 0.1875;\n"; + code += " if (index == 3) limit = 0.6875;\n"; + code += " if (index == 4) limit = 0.8125;\n"; + code += " if (index == 5) limit = 0.3125;\n"; + code += " if (index == 6) limit = 0.9375;\n"; + code += " if (index == 7) limit = 0.4375;\n"; + code += " if (index == 8) limit = 0.25;\n"; + code += " if (index == 9) limit = 0.75;\n"; + code += " if (index == 10) limit = 0.125;\n"; + code += " if (index == 11) limit = 0.625;\n"; + code += " if (index == 12) limit = 1.0;\n"; + code += " if (index == 13) limit = 0.5;\n"; + code += " if (index == 14) limit = 0.875;\n"; + code += " if (index == 15) limit = 0.375;\n"; + code += " }\n\n"; + code += " if (fade < limit)\n"; + code += " discard;\n"; + code += " }\n\n"; } } else { - code += "\tALPHA*=clamp(smoothstep(distance_fade_min,distance_fade_max,-VERTEX.z),0.0,1.0);\n"; + code += " ALPHA*=clamp(smoothstep(distance_fade_min,distance_fade_max,-VERTEX.z),0.0,1.0);\n"; } } if (features[FEATURE_RIM]) { if (flags[FLAG_UV1_USE_TRIPLANAR]) { - code += "\tvec2 rim_tex = triplanar_texture(texture_rim,uv1_power_normal,uv1_triplanar_pos).xy;\n"; + code += " vec2 rim_tex = triplanar_texture(texture_rim,uv1_power_normal,uv1_triplanar_pos).xy;\n"; } else { - code += "\tvec2 rim_tex = texture(texture_rim,base_uv).xy;\n"; + code += " vec2 rim_tex = texture(texture_rim,base_uv).xy;\n"; } - code += "\tRIM = rim*rim_tex.x;"; - code += "\tRIM_TINT = rim_tint*rim_tex.y;\n"; + code += " RIM = rim*rim_tex.x;"; + code += " RIM_TINT = rim_tint*rim_tex.y;\n"; } if (features[FEATURE_CLEARCOAT]) { if (flags[FLAG_UV1_USE_TRIPLANAR]) { - code += "\tvec2 clearcoat_tex = triplanar_texture(texture_clearcoat,uv1_power_normal,uv1_triplanar_pos).xy;\n"; + code += " vec2 clearcoat_tex = triplanar_texture(texture_clearcoat,uv1_power_normal,uv1_triplanar_pos).xy;\n"; } else { - code += "\tvec2 clearcoat_tex = texture(texture_clearcoat,base_uv).xy;\n"; + code += " vec2 clearcoat_tex = texture(texture_clearcoat,base_uv).xy;\n"; } - code += "\tCLEARCOAT = clearcoat*clearcoat_tex.x;"; - code += "\tCLEARCOAT_GLOSS = clearcoat_gloss*clearcoat_tex.y;\n"; + code += " CLEARCOAT = clearcoat*clearcoat_tex.x;"; + code += " CLEARCOAT_GLOSS = clearcoat_gloss*clearcoat_tex.y;\n"; } if (features[FEATURE_ANISOTROPY]) { if (flags[FLAG_UV1_USE_TRIPLANAR]) { - code += "\tvec3 anisotropy_tex = triplanar_texture(texture_flowmap,uv1_power_normal,uv1_triplanar_pos).rga;\n"; + code += " vec3 anisotropy_tex = triplanar_texture(texture_flowmap,uv1_power_normal,uv1_triplanar_pos).rga;\n"; } else { - code += "\tvec3 anisotropy_tex = texture(texture_flowmap,base_uv).rga;\n"; + code += " vec3 anisotropy_tex = texture(texture_flowmap,base_uv).rga;\n"; } - code += "\tANISOTROPY = anisotropy_ratio*anisotropy_tex.b;\n"; - code += "\tANISOTROPY_FLOW = anisotropy_tex.rg*2.0-1.0;\n"; + code += " ANISOTROPY = anisotropy_ratio*anisotropy_tex.b;\n"; + code += " ANISOTROPY_FLOW = anisotropy_tex.rg*2.0-1.0;\n"; } if (features[FEATURE_AMBIENT_OCCLUSION]) { if (!orm) { if (flags[FLAG_AO_ON_UV2]) { if (flags[FLAG_UV2_USE_TRIPLANAR]) { - code += "\tAO = dot(triplanar_texture(texture_ambient_occlusion,uv2_power_normal,uv2_triplanar_pos),ao_texture_channel);\n"; + code += " AO = dot(triplanar_texture(texture_ambient_occlusion,uv2_power_normal,uv2_triplanar_pos),ao_texture_channel);\n"; } else { - code += "\tAO = dot(texture(texture_ambient_occlusion,base_uv2),ao_texture_channel);\n"; + code += " AO = dot(texture(texture_ambient_occlusion,base_uv2),ao_texture_channel);\n"; } } else { if (flags[FLAG_UV1_USE_TRIPLANAR]) { - code += "\tAO = dot(triplanar_texture(texture_ambient_occlusion,uv1_power_normal,uv1_triplanar_pos),ao_texture_channel);\n"; + code += " AO = dot(triplanar_texture(texture_ambient_occlusion,uv1_power_normal,uv1_triplanar_pos),ao_texture_channel);\n"; } else { - code += "\tAO = dot(texture(texture_ambient_occlusion,base_uv),ao_texture_channel);\n"; + code += " AO = dot(texture(texture_ambient_occlusion,base_uv),ao_texture_channel);\n"; } } } else { - code += "\tAO = orm_tex.r;\n"; + code += " AO = orm_tex.r;\n"; } - code += "\tAO_LIGHT_AFFECT = ao_light_affect;\n"; + code += " AO_LIGHT_AFFECT = ao_light_affect;\n"; } if (features[FEATURE_SUBSURFACE_SCATTERING]) { if (flags[FLAG_UV1_USE_TRIPLANAR]) { - code += "\tfloat sss_tex = triplanar_texture(texture_subsurface_scattering,uv1_power_normal,uv1_triplanar_pos).r;\n"; + code += " float sss_tex = triplanar_texture(texture_subsurface_scattering,uv1_power_normal,uv1_triplanar_pos).r;\n"; } else { - code += "\tfloat sss_tex = texture(texture_subsurface_scattering,base_uv).r;\n"; + code += " float sss_tex = texture(texture_subsurface_scattering,base_uv).r;\n"; } - code += "\tSSS_STRENGTH=subsurface_scattering_strength*sss_tex;\n"; + code += " SSS_STRENGTH=subsurface_scattering_strength*sss_tex;\n"; } if (features[FEATURE_SUBSURFACE_TRANSMITTANCE]) { if (flags[FLAG_UV1_USE_TRIPLANAR]) { - code += "\tvec4 trans_color_tex = triplanar_texture(texture_subsurface_transmittance,uv1_power_normal,uv1_triplanar_pos);\n"; + code += " vec4 trans_color_tex = triplanar_texture(texture_subsurface_transmittance,uv1_power_normal,uv1_triplanar_pos);\n"; } else { - code += "\tvec4 trans_color_tex = texture(texture_subsurface_transmittance,base_uv);\n"; + code += " vec4 trans_color_tex = texture(texture_subsurface_transmittance,base_uv);\n"; } - code += "\tSSS_TRANSMITTANCE_COLOR=transmittance_color*trans_color_tex;\n"; + code += " SSS_TRANSMITTANCE_COLOR=transmittance_color*trans_color_tex;\n"; - code += "\tSSS_TRANSMITTANCE_DEPTH=transmittance_depth;\n"; - code += "\tSSS_TRANSMITTANCE_BOOST=transmittance_boost;\n"; + code += " SSS_TRANSMITTANCE_DEPTH=transmittance_depth;\n"; + code += " SSS_TRANSMITTANCE_BOOST=transmittance_boost;\n"; } if (features[FEATURE_BACKLIGHT]) { if (flags[FLAG_UV1_USE_TRIPLANAR]) { - code += "\tvec3 backlight_tex = triplanar_texture(texture_backlight,uv1_power_normal,uv1_triplanar_pos).rgb;\n"; + code += " vec3 backlight_tex = triplanar_texture(texture_backlight,uv1_power_normal,uv1_triplanar_pos).rgb;\n"; } else { - code += "\tvec3 backlight_tex = texture(texture_backlight,base_uv).rgb;\n"; + code += " vec3 backlight_tex = texture(texture_backlight,base_uv).rgb;\n"; } - code += "\tBACKLIGHT = (backlight.rgb+backlight_tex);\n"; + code += " BACKLIGHT = (backlight.rgb+backlight_tex);\n"; } if (features[FEATURE_DETAIL]) { @@ -1209,41 +1242,41 @@ void BaseMaterial3D::_update_shader() { if (triplanar) { String tp_uv = detail_uv == DETAIL_UV_1 ? "uv1" : "uv2"; - code += "\tvec4 detail_tex = triplanar_texture(texture_detail_albedo," + tp_uv + "_power_normal," + tp_uv + "_triplanar_pos);\n"; - code += "\tvec4 detail_norm_tex = triplanar_texture(texture_detail_normal," + tp_uv + "_power_normal," + tp_uv + "_triplanar_pos);\n"; + code += " vec4 detail_tex = triplanar_texture(texture_detail_albedo," + tp_uv + "_power_normal," + tp_uv + "_triplanar_pos);\n"; + code += " vec4 detail_norm_tex = triplanar_texture(texture_detail_normal," + tp_uv + "_power_normal," + tp_uv + "_triplanar_pos);\n"; } else { String det_uv = detail_uv == DETAIL_UV_1 ? "base_uv" : "base_uv2"; - code += "\tvec4 detail_tex = texture(texture_detail_albedo," + det_uv + ");\n"; - code += "\tvec4 detail_norm_tex = texture(texture_detail_normal," + det_uv + ");\n"; + code += " vec4 detail_tex = texture(texture_detail_albedo," + det_uv + ");\n"; + code += " vec4 detail_norm_tex = texture(texture_detail_normal," + det_uv + ");\n"; } if (flags[FLAG_UV1_USE_TRIPLANAR]) { - code += "\tvec4 detail_mask_tex = triplanar_texture(texture_detail_mask,uv1_power_normal,uv1_triplanar_pos);\n"; + code += " vec4 detail_mask_tex = triplanar_texture(texture_detail_mask,uv1_power_normal,uv1_triplanar_pos);\n"; } else { - code += "\tvec4 detail_mask_tex = texture(texture_detail_mask,base_uv);\n"; + code += " vec4 detail_mask_tex = texture(texture_detail_mask,base_uv);\n"; } switch (detail_blend_mode) { case BLEND_MODE_MIX: { - code += "\tvec3 detail = mix(ALBEDO.rgb,detail_tex.rgb,detail_tex.a);\n"; + code += " vec3 detail = mix(ALBEDO.rgb,detail_tex.rgb,detail_tex.a);\n"; } break; case BLEND_MODE_ADD: { - code += "\tvec3 detail = mix(ALBEDO.rgb,ALBEDO.rgb+detail_tex.rgb,detail_tex.a);\n"; + code += " vec3 detail = mix(ALBEDO.rgb,ALBEDO.rgb+detail_tex.rgb,detail_tex.a);\n"; } break; case BLEND_MODE_SUB: { - code += "\tvec3 detail = mix(ALBEDO.rgb,ALBEDO.rgb-detail_tex.rgb,detail_tex.a);\n"; + code += " vec3 detail = mix(ALBEDO.rgb,ALBEDO.rgb-detail_tex.rgb,detail_tex.a);\n"; } break; case BLEND_MODE_MUL: { - code += "\tvec3 detail = mix(ALBEDO.rgb,ALBEDO.rgb*detail_tex.rgb,detail_tex.a);\n"; + code += " vec3 detail = mix(ALBEDO.rgb,ALBEDO.rgb*detail_tex.rgb,detail_tex.a);\n"; } break; case BLEND_MODE_MAX: break; // Internal value, skip. } - code += "\tvec3 detail_norm = mix(NORMAL_MAP,detail_norm_tex.rgb,detail_tex.a);\n"; - code += "\tNORMAL_MAP = mix(NORMAL_MAP,detail_norm,detail_mask_tex.r);\n"; - code += "\tALBEDO.rgb = mix(ALBEDO.rgb,detail,detail_mask_tex.r);\n"; + code += " vec3 detail_norm = mix(NORMAL_MAP,detail_norm_tex.rgb,detail_tex.a);\n"; + code += " NORMAL_MAP = mix(NORMAL_MAP,detail_norm,detail_mask_tex.r);\n"; + code += " ALBEDO.rgb = mix(ALBEDO.rgb,detail,detail_mask_tex.r);\n"; } code += "}\n"; diff --git a/scene/resources/material.h b/scene/resources/material.h index 80e5e94466..e2838e1399 100644 --- a/scene/resources/material.h +++ b/scene/resources/material.h @@ -79,6 +79,8 @@ class ShaderMaterial : public Material { GDCLASS(ShaderMaterial, Material); Ref<Shader> shader; + Map<StringName, Variant> param_cache; + protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index c5bfbc39db..8f3f25f104 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -30,6 +30,7 @@ #include "mesh.h" +#include "core/math/convex_hull.h" #include "core/templates/pair.h" #include "scene/resources/concave_polygon_shape_3d.h" #include "scene/resources/convex_polygon_shape_3d.h" @@ -37,7 +38,7 @@ #include <stdlib.h> -Mesh::ConvexDecompositionFunc Mesh::convex_composition_function = nullptr; +Mesh::ConvexDecompositionFunc Mesh::convex_decomposition_function = nullptr; Ref<TriangleMesh> Mesh::generate_triangle_mesh() const { if (triangle_mesh.is_valid()) { @@ -166,64 +167,21 @@ Vector<Face3> Mesh::get_faces() const { return tm->get_faces(); } return Vector<Face3>(); - /* - for (int i=0;i<surfaces.size();i++) { - if (RenderingServer::get_singleton()->mesh_surface_get_primitive_type( mesh, i ) != RenderingServer::PRIMITIVE_TRIANGLES ) - continue; - - Vector<int> indices; - Vector<Vector3> vertices; - - vertices=RenderingServer::get_singleton()->mesh_surface_get_array(mesh, i,RenderingServer::ARRAY_VERTEX); - - int len=RenderingServer::get_singleton()->mesh_surface_get_array_index_len(mesh, i); - bool has_indices; - - if (len>0) { - indices=RenderingServer::get_singleton()->mesh_surface_get_array(mesh, i,RenderingServer::ARRAY_INDEX); - has_indices=true; +} +Ref<Shape3D> Mesh::create_convex_shape(bool p_clean, bool p_simplify) const { + if (p_simplify) { + ConvexDecompositionSettings settings; + settings.max_convex_hulls = 1; + Vector<Ref<Shape3D>> decomposed = convex_decompose(settings); + if (decomposed.size() == 1) { + return decomposed[0]; } else { - len=vertices.size(); - has_indices=false; - } - - if (len<=0) - continue; - - const int* indicesr = indices.ptr(); - const int *indicesptr = indicesr.ptr(); - - const Vector3* verticesr = vertices.ptr(); - const Vector3 *verticesptr = verticesr.ptr(); - - int old_faces=faces.size(); - int new_faces=old_faces+(len/3); - - faces.resize(new_faces); - - Face3* facesw = faces.ptrw(); - Face3 *facesptr=facesw.ptr(); - - - for (int i=0;i<len/3;i++) { - Face3 face; - - for (int j=0;j<3;j++) { - int idx=i*3+j; - face.vertex[j] = has_indices ? verticesptr[ indicesptr[ idx ] ] : verticesptr[idx]; - } - - facesptr[i+old_faces]=face; + ERR_PRINT("Convex shape simplification failed, falling back to simpler process."); } - } -*/ -} -Ref<Shape3D> Mesh::create_convex_shape() const { Vector<Vector3> vertices; - for (int i = 0; i < get_surface_count(); i++) { Array a = surface_get_arrays(i); ERR_FAIL_COND_V(a.is_empty(), Ref<ConvexPolygonShape3D>()); @@ -232,6 +190,18 @@ Ref<Shape3D> Mesh::create_convex_shape() const { } Ref<ConvexPolygonShape3D> shape = memnew(ConvexPolygonShape3D); + + if (p_clean) { + Geometry3D::MeshData md; + Error err = ConvexHullComputer::convex_hull(vertices, md); + if (err == OK) { + shape->set_points(md.vertices); + return shape; + } else { + ERR_PRINT("Convex shape cleaning failed, falling back to simpler process."); + } + } + shape->set_points(vertices); return shape; } @@ -522,6 +492,7 @@ void Mesh::_bind_methods() { BIND_ENUM_CONSTANT(ARRAY_FORMAT_BLEND_SHAPE_MASK); BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM_BASE); + BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM_BITS); BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM0_SHIFT); BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM1_SHIFT); BIND_ENUM_CONSTANT(ARRAY_FORMAT_CUSTOM2_SHIFT); @@ -543,36 +514,37 @@ void Mesh::clear_cache() const { debug_lines.clear(); } -Vector<Ref<Shape3D>> Mesh::convex_decompose() const { - ERR_FAIL_COND_V(!convex_composition_function, Vector<Ref<Shape3D>>()); - - const Vector<Face3> faces = get_faces(); +Vector<Ref<Shape3D>> Mesh::convex_decompose(const ConvexDecompositionSettings &p_settings) const { + ERR_FAIL_COND_V(!convex_decomposition_function, Vector<Ref<Shape3D>>()); - Vector<Vector<Face3>> decomposed = convex_composition_function(faces); - - Vector<Ref<Shape3D>> ret; + Ref<TriangleMesh> tm = generate_triangle_mesh(); + ERR_FAIL_COND_V(!tm.is_valid(), Vector<Ref<Shape3D>>()); - for (int i = 0; i < decomposed.size(); i++) { - Set<Vector3> points; - for (int j = 0; j < decomposed[i].size(); j++) { - points.insert(decomposed[i][j].vertex[0]); - points.insert(decomposed[i][j].vertex[1]); - points.insert(decomposed[i][j].vertex[2]); - } + const Vector<TriangleMesh::Triangle> &triangles = tm->get_triangles(); + int triangle_count = triangles.size(); - Vector<Vector3> convex_points; - convex_points.resize(points.size()); - { - Vector3 *w = convex_points.ptrw(); - int idx = 0; - for (Set<Vector3>::Element *E = points.front(); E; E = E->next()) { - w[idx++] = E->get(); + Vector<uint32_t> indices; + { + indices.resize(triangle_count * 3); + uint32_t *w = indices.ptrw(); + for (int i = 0; i < triangle_count; i++) { + for (int j = 0; j < 3; j++) { + w[i * 3 + j] = triangles[i].indices[j]; } } + } + + const Vector<Vector3> &vertices = tm->get_vertices(); + int vertex_count = vertices.size(); + + Vector<Vector<Vector3>> decomposed = convex_decomposition_function((real_t *)vertices.ptr(), vertex_count, indices.ptr(), triangle_count, p_settings, nullptr); + Vector<Ref<Shape3D>> ret; + + for (int i = 0; i < decomposed.size(); i++) { Ref<ConvexPolygonShape3D> shape; shape.instantiate(); - shape->set_points(convex_points); + shape->set_points(decomposed[i]); ret.push_back(shape); } @@ -632,6 +604,7 @@ enum OldArrayFormat { }; +#ifndef DISABLE_DEPRECATED static Array _convert_old_array(const Array &p_old) { Array new_array; new_array.resize(Mesh::ARRAY_MAX); @@ -656,6 +629,7 @@ static Mesh::PrimitiveType _old_primitives[7] = { Mesh::PRIMITIVE_TRIANGLE_STRIP, Mesh::PRIMITIVE_TRIANGLE_STRIP }; +#endif // DISABLE_DEPRECATED void _fix_array_compatibility(const Vector<uint8_t> &p_src, uint32_t p_old_format, uint32_t p_new_format, uint32_t p_elements, Vector<uint8_t> &vertex_data, Vector<uint8_t> &attribute_data, Vector<uint8_t> &skin_data) { uint32_t dst_vertex_stride; @@ -1314,9 +1288,9 @@ void ArrayMesh::_get_property_list(List<PropertyInfo> *p_list) const { for (int i = 0; i < surfaces.size(); i++) { p_list->push_back(PropertyInfo(Variant::STRING, "surface_" + itos(i + 1) + "/name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); if (surfaces[i].is_2d) { - p_list->push_back(PropertyInfo(Variant::OBJECT, "surface_" + itos(i + 1) + "/material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,CanvasItemMaterial", PROPERTY_USAGE_EDITOR)); + p_list->push_back(PropertyInfo(Variant::OBJECT, "surface_" + itos(i + 1) + "/material", PROPERTY_HINT_RESOURCE_TYPE, "CanvasItemMaterial,ShaderMaterial", PROPERTY_USAGE_EDITOR)); } else { - p_list->push_back(PropertyInfo(Variant::OBJECT, "surface_" + itos(i + 1) + "/material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,StandardMaterial3D", PROPERTY_USAGE_EDITOR)); + p_list->push_back(PropertyInfo(Variant::OBJECT, "surface_" + itos(i + 1) + "/material", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial", PROPERTY_USAGE_EDITOR)); } } } @@ -1852,7 +1826,7 @@ void ArrayMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("surface_set_name", "surf_idx", "name"), &ArrayMesh::surface_set_name); ClassDB::bind_method(D_METHOD("surface_get_name", "surf_idx"), &ArrayMesh::surface_get_name); ClassDB::bind_method(D_METHOD("create_trimesh_shape"), &ArrayMesh::create_trimesh_shape); - ClassDB::bind_method(D_METHOD("create_convex_shape"), &ArrayMesh::create_convex_shape); + ClassDB::bind_method(D_METHOD("create_convex_shape", "clean", "simplify"), &ArrayMesh::create_convex_shape, DEFVAL(true), DEFVAL(false)); ClassDB::bind_method(D_METHOD("create_outline", "margin"), &ArrayMesh::create_outline); ClassDB::bind_method(D_METHOD("regen_normal_maps"), &ArrayMesh::regen_normal_maps); ClassDB::set_method_flags(get_class_static(), _scs_create("regen_normal_maps"), METHOD_FLAGS_DEFAULT | METHOD_FLAG_EDITOR); diff --git a/scene/resources/mesh.h b/scene/resources/mesh.h index 02cab9a5e1..0776585a11 100644 --- a/scene/resources/mesh.h +++ b/scene/resources/mesh.h @@ -105,6 +105,7 @@ public: ARRAY_FORMAT_BLEND_SHAPE_MASK = RS::ARRAY_FORMAT_BLEND_SHAPE_MASK, ARRAY_FORMAT_CUSTOM_BASE = RS::ARRAY_FORMAT_CUSTOM_BASE, + ARRAY_FORMAT_CUSTOM_BITS = RS::ARRAY_FORMAT_CUSTOM_BITS, ARRAY_FORMAT_CUSTOM0_SHIFT = RS::ARRAY_FORMAT_CUSTOM0_SHIFT, ARRAY_FORMAT_CUSTOM1_SHIFT = RS::ARRAY_FORMAT_CUSTOM1_SHIFT, ARRAY_FORMAT_CUSTOM2_SHIFT = RS::ARRAY_FORMAT_CUSTOM2_SHIFT, @@ -149,7 +150,7 @@ public: void generate_debug_mesh_indices(Vector<Vector3> &r_points); Ref<Shape3D> create_trimesh_shape() const; - Ref<Shape3D> create_convex_shape() const; + Ref<Shape3D> create_convex_shape(bool p_clean = true, bool p_simplify = false) const; Ref<Mesh> create_outline(float p_margin) const; @@ -159,11 +160,42 @@ public: Size2i get_lightmap_size_hint() const; void clear_cache() const; - typedef Vector<Vector<Face3>> (*ConvexDecompositionFunc)(const Vector<Face3> &); + struct ConvexDecompositionSettings { + enum Mode : int { + CONVEX_DECOMPOSITION_MODE_VOXEL = 0, + CONVEX_DECOMPOSITION_MODE_TETRAHEDRON + }; + + /// Maximum concavity. [Range: 0.0 -> 1.0] + real_t max_concavity = 1.0; + /// Controls the bias toward clipping along symmetry planes. [Range: 0.0 -> 1.0] + real_t symmetry_planes_clipping_bias = 0.05; + /// Controls the bias toward clipping along revolution axes. [Range: 0.0 -> 1.0] + real_t revolution_axes_clipping_bias = 0.05; + real_t min_volume_per_convex_hull = 0.0001; + /// Maximum number of voxels generated during the voxelization stage. + uint32_t resolution = 10'000; + uint32_t max_num_vertices_per_convex_hull = 32; + /// Controls the granularity of the search for the "best" clipping plane. + /// [Range: 1 -> 16] + uint32_t plane_downsampling = 4; + /// Controls the precision of the convex-hull generation process during the + /// clipping plane selection stage. + /// [Range: 1 -> 16] + uint32_t convexhull_downsampling = 4; + /// enable/disable normalizing the mesh before applying the convex decomposition. + bool normalize_mesh = false; + Mode mode = CONVEX_DECOMPOSITION_MODE_VOXEL; + bool convexhull_approximation = true; + /// This is the maximum number of convex hulls to produce from the merge operation. + uint32_t max_convex_hulls = 1; + bool project_hull_vertices = true; + }; + typedef Vector<Vector<Vector3>> (*ConvexDecompositionFunc)(const real_t *p_vertices, int p_vertex_count, const uint32_t *p_triangles, int p_triangle_count, const ConvexDecompositionSettings &p_settings, Vector<Vector<uint32_t>> *r_convex_indices); - static ConvexDecompositionFunc convex_composition_function; + static ConvexDecompositionFunc convex_decomposition_function; - Vector<Ref<Shape3D>> convex_decompose() const; + Vector<Ref<Shape3D>> convex_decompose(const ConvexDecompositionSettings &p_settings) const; virtual int get_builtin_bind_pose_count() const; virtual Transform3D get_builtin_bind_pose(int p_index) const; diff --git a/scene/resources/mesh_data_tool.cpp b/scene/resources/mesh_data_tool.cpp index 3fb4f8f211..04b2437ae8 100644 --- a/scene/resources/mesh_data_tool.cpp +++ b/scene/resources/mesh_data_tool.cpp @@ -107,9 +107,9 @@ Error MeshDataTool::create_from_surface(const Ref<ArrayMesh> &p_mesh, int p_surf bo = arrays[Mesh::ARRAY_BONES].operator Vector<int>().ptr(); } - const real_t *we = nullptr; + const float *we = nullptr; if (arrays[Mesh::ARRAY_WEIGHTS].get_type() != Variant::NIL) { - we = arrays[Mesh::ARRAY_WEIGHTS].operator Vector<real_t>().ptr(); + we = arrays[Mesh::ARRAY_WEIGHTS].operator Vector<float>().ptr(); } vertices.resize(vcount); diff --git a/scene/resources/mesh_library.cpp b/scene/resources/mesh_library.cpp index 33c9ca6d1e..cfb7c3e037 100644 --- a/scene/resources/mesh_library.cpp +++ b/scene/resources/mesh_library.cpp @@ -43,6 +43,8 @@ bool MeshLibrary::_set(const StringName &p_name, const Variant &p_value) { set_item_name(idx, p_value); } else if (what == "mesh") { set_item_mesh(idx, p_value); + } else if (what == "mesh_transform") { + set_item_mesh_transform(idx, p_value); } else if (what == "shape") { Vector<ShapeData> shapes; ShapeData sd; @@ -77,6 +79,8 @@ bool MeshLibrary::_get(const StringName &p_name, Variant &r_ret) const { r_ret = get_item_name(idx); } else if (what == "mesh") { r_ret = get_item_mesh(idx); + } else if (what == "mesh_transform") { + r_ret = get_item_mesh_transform(idx); } else if (what == "shapes") { r_ret = _get_item_shapes(idx); } else if (what == "navmesh") { @@ -127,6 +131,14 @@ void MeshLibrary::set_item_mesh(int p_item, const Ref<Mesh> &p_mesh) { notify_property_list_changed(); } +void MeshLibrary::set_item_mesh_transform(int p_item, const Transform3D &p_transform) { + ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + item_map[p_item].mesh_transform = p_transform; + notify_change_to_owners(); + emit_changed(); + notify_property_list_changed(); +} + void MeshLibrary::set_item_shapes(int p_item, const Vector<ShapeData> &p_shapes) { ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); item_map[p_item].shapes = p_shapes; @@ -170,6 +182,11 @@ Ref<Mesh> MeshLibrary::get_item_mesh(int p_item) const { return item_map[p_item].mesh; } +Transform3D MeshLibrary::get_item_mesh_transform(int p_item) const { + ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Transform3D(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + return item_map[p_item].mesh_transform; +} + Vector<MeshLibrary::ShapeData> MeshLibrary::get_item_shapes(int p_item) const { ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Vector<ShapeData>(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); return item_map[p_item].shapes; @@ -271,12 +288,14 @@ void MeshLibrary::_bind_methods() { ClassDB::bind_method(D_METHOD("create_item", "id"), &MeshLibrary::create_item); ClassDB::bind_method(D_METHOD("set_item_name", "id", "name"), &MeshLibrary::set_item_name); ClassDB::bind_method(D_METHOD("set_item_mesh", "id", "mesh"), &MeshLibrary::set_item_mesh); + ClassDB::bind_method(D_METHOD("set_item_mesh_transform", "id", "mesh_transform"), &MeshLibrary::set_item_mesh_transform); ClassDB::bind_method(D_METHOD("set_item_navmesh", "id", "navmesh"), &MeshLibrary::set_item_navmesh); ClassDB::bind_method(D_METHOD("set_item_navmesh_transform", "id", "navmesh"), &MeshLibrary::set_item_navmesh_transform); ClassDB::bind_method(D_METHOD("set_item_shapes", "id", "shapes"), &MeshLibrary::_set_item_shapes); ClassDB::bind_method(D_METHOD("set_item_preview", "id", "texture"), &MeshLibrary::set_item_preview); ClassDB::bind_method(D_METHOD("get_item_name", "id"), &MeshLibrary::get_item_name); ClassDB::bind_method(D_METHOD("get_item_mesh", "id"), &MeshLibrary::get_item_mesh); + ClassDB::bind_method(D_METHOD("get_item_mesh_transform", "id"), &MeshLibrary::get_item_mesh_transform); ClassDB::bind_method(D_METHOD("get_item_navmesh", "id"), &MeshLibrary::get_item_navmesh); ClassDB::bind_method(D_METHOD("get_item_navmesh_transform", "id"), &MeshLibrary::get_item_navmesh_transform); ClassDB::bind_method(D_METHOD("get_item_shapes", "id"), &MeshLibrary::_get_item_shapes); diff --git a/scene/resources/mesh_library.h b/scene/resources/mesh_library.h index 1e8a6bf3ff..c25df757e9 100644 --- a/scene/resources/mesh_library.h +++ b/scene/resources/mesh_library.h @@ -52,6 +52,7 @@ public: Vector<ShapeData> shapes; Ref<Texture2D> preview; Transform3D navmesh_transform; + Transform3D mesh_transform; Ref<NavigationMesh> navmesh; }; @@ -72,12 +73,14 @@ public: void create_item(int p_item); void set_item_name(int p_item, const String &p_name); void set_item_mesh(int p_item, const Ref<Mesh> &p_mesh); + void set_item_mesh_transform(int p_item, const Transform3D &p_transform); void set_item_navmesh(int p_item, const Ref<NavigationMesh> &p_navmesh); void set_item_navmesh_transform(int p_item, const Transform3D &p_transform); void set_item_shapes(int p_item, const Vector<ShapeData> &p_shapes); void set_item_preview(int p_item, const Ref<Texture2D> &p_preview); String get_item_name(int p_item) const; Ref<Mesh> get_item_mesh(int p_item) const; + Transform3D get_item_mesh_transform(int p_item) const; Ref<NavigationMesh> get_item_navmesh(int p_item) const; Transform3D get_item_navmesh_transform(int p_item) const; Vector<ShapeData> get_item_shapes(int p_item) const; diff --git a/scene/resources/navigation_mesh.cpp b/scene/resources/navigation_mesh.cpp index d2be2bdba1..00cee9269b 100644 --- a/scene/resources/navigation_mesh.cpp +++ b/scene/resources/navigation_mesh.cpp @@ -64,22 +64,22 @@ void NavigationMesh::create_from_mesh(const Ref<Mesh> &p_mesh) { } } -void NavigationMesh::set_sample_partition_type(int p_value) { - ERR_FAIL_COND(p_value >= SAMPLE_PARTITION_MAX); - partition_type = static_cast<SamplePartitionType>(p_value); +void NavigationMesh::set_sample_partition_type(SamplePartitionType p_value) { + ERR_FAIL_INDEX(p_value, SAMPLE_PARTITION_MAX); + partition_type = p_value; } -int NavigationMesh::get_sample_partition_type() const { - return static_cast<int>(partition_type); +NavigationMesh::SamplePartitionType NavigationMesh::get_sample_partition_type() const { + return partition_type; } -void NavigationMesh::set_parsed_geometry_type(int p_value) { - ERR_FAIL_COND(p_value >= PARSED_GEOMETRY_MAX); - parsed_geometry_type = static_cast<ParsedGeometryType>(p_value); +void NavigationMesh::set_parsed_geometry_type(ParsedGeometryType p_value) { + ERR_FAIL_INDEX(p_value, PARSED_GEOMETRY_MAX); + parsed_geometry_type = p_value; notify_property_list_changed(); } -int NavigationMesh::get_parsed_geometry_type() const { +NavigationMesh::ParsedGeometryType NavigationMesh::get_parsed_geometry_type() const { return parsed_geometry_type; } @@ -91,29 +91,31 @@ uint32_t NavigationMesh::get_collision_mask() const { return collision_mask; } -void NavigationMesh::set_collision_mask_bit(int p_bit, bool p_value) { - ERR_FAIL_INDEX_MSG(p_bit, 32, "Collision mask bit must be between 0 and 31 inclusive."); +void NavigationMesh::set_collision_mask_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive."); uint32_t mask = get_collision_mask(); if (p_value) { - mask |= 1 << p_bit; + mask |= 1 << (p_layer_number - 1); } else { - mask &= ~(1 << p_bit); + mask &= ~(1 << (p_layer_number - 1)); } set_collision_mask(mask); } -bool NavigationMesh::get_collision_mask_bit(int p_bit) const { - ERR_FAIL_INDEX_V_MSG(p_bit, 32, false, "Collision mask bit must be between 0 and 31 inclusive."); - return get_collision_mask() & (1 << p_bit); +bool NavigationMesh::get_collision_mask_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Collision layer number must be between 1 and 32 inclusive."); + return get_collision_mask() & (1 << (p_layer_number - 1)); } -void NavigationMesh::set_source_geometry_mode(int p_geometry_mode) { +void NavigationMesh::set_source_geometry_mode(SourceGeometryMode p_geometry_mode) { ERR_FAIL_INDEX(p_geometry_mode, SOURCE_GEOMETRY_MAX); - source_geometry_mode = static_cast<SourceGeometryMode>(p_geometry_mode); + source_geometry_mode = p_geometry_mode; notify_property_list_changed(); } -int NavigationMesh::get_source_geometry_mode() const { +NavigationMesh::SourceGeometryMode NavigationMesh::get_source_geometry_mode() const { return source_geometry_mode; } @@ -126,6 +128,7 @@ StringName NavigationMesh::get_source_group_name() const { } void NavigationMesh::set_cell_size(float p_value) { + ERR_FAIL_COND(p_value <= 0); cell_size = p_value; } @@ -134,6 +137,7 @@ float NavigationMesh::get_cell_size() const { } void NavigationMesh::set_cell_height(float p_value) { + ERR_FAIL_COND(p_value <= 0); cell_height = p_value; } @@ -142,6 +146,7 @@ float NavigationMesh::get_cell_height() const { } void NavigationMesh::set_agent_height(float p_value) { + ERR_FAIL_COND(p_value < 0); agent_height = p_value; } @@ -150,6 +155,7 @@ float NavigationMesh::get_agent_height() const { } void NavigationMesh::set_agent_radius(float p_value) { + ERR_FAIL_COND(p_value < 0); agent_radius = p_value; } @@ -158,6 +164,7 @@ float NavigationMesh::get_agent_radius() { } void NavigationMesh::set_agent_max_climb(float p_value) { + ERR_FAIL_COND(p_value < 0); agent_max_climb = p_value; } @@ -166,6 +173,7 @@ float NavigationMesh::get_agent_max_climb() const { } void NavigationMesh::set_agent_max_slope(float p_value) { + ERR_FAIL_COND(p_value < 0 || p_value > 90); agent_max_slope = p_value; } @@ -174,6 +182,7 @@ float NavigationMesh::get_agent_max_slope() const { } void NavigationMesh::set_region_min_size(float p_value) { + ERR_FAIL_COND(p_value < 0); region_min_size = p_value; } @@ -182,6 +191,7 @@ float NavigationMesh::get_region_min_size() const { } void NavigationMesh::set_region_merge_size(float p_value) { + ERR_FAIL_COND(p_value < 0); region_merge_size = p_value; } @@ -190,6 +200,7 @@ float NavigationMesh::get_region_merge_size() const { } void NavigationMesh::set_edge_max_length(float p_value) { + ERR_FAIL_COND(p_value < 0); edge_max_length = p_value; } @@ -198,6 +209,7 @@ float NavigationMesh::get_edge_max_length() const { } void NavigationMesh::set_edge_max_error(float p_value) { + ERR_FAIL_COND(p_value < 0); edge_max_error = p_value; } @@ -206,6 +218,7 @@ float NavigationMesh::get_edge_max_error() const { } void NavigationMesh::set_verts_per_poly(float p_value) { + ERR_FAIL_COND(p_value < 3); verts_per_poly = p_value; } @@ -214,6 +227,7 @@ float NavigationMesh::get_verts_per_poly() const { } void NavigationMesh::set_detail_sample_distance(float p_value) { + ERR_FAIL_COND(p_value < 0); detail_sample_distance = p_value; } @@ -222,6 +236,7 @@ float NavigationMesh::get_detail_sample_distance() const { } void NavigationMesh::set_detail_sample_max_error(float p_value) { + ERR_FAIL_COND(p_value < 0); detail_sample_max_error = p_value; } @@ -329,9 +344,7 @@ Ref<Mesh> NavigationMesh::get_debug_mesh() { Vector3 *tw = tmeshfaces.ptrw(); int tidx = 0; - for (List<Face3>::Element *E = faces.front(); E; E = E->next()) { - const Face3 &f = E->get(); - + for (const Face3 &f : faces) { for (int j = 0; j < 3; j++) { tw[tidx++] = f.vertex[j]; _EdgeKey ek; @@ -366,8 +379,8 @@ Ref<Mesh> NavigationMesh::get_debug_mesh() { { Vector3 *w = varr.ptrw(); int idx = 0; - for (List<Vector3>::Element *E = lines.front(); E; E = E->next()) { - w[idx++] = E->get(); + for (const Vector3 &E : lines) { + w[idx++] = E; } } @@ -392,8 +405,8 @@ void NavigationMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &NavigationMesh::set_collision_mask); ClassDB::bind_method(D_METHOD("get_collision_mask"), &NavigationMesh::get_collision_mask); - ClassDB::bind_method(D_METHOD("set_collision_mask_bit", "bit", "value"), &NavigationMesh::set_collision_mask_bit); - ClassDB::bind_method(D_METHOD("get_collision_mask_bit", "bit"), &NavigationMesh::get_collision_mask_bit); + ClassDB::bind_method(D_METHOD("set_collision_mask_value", "layer_number", "value"), &NavigationMesh::set_collision_mask_value); + ClassDB::bind_method(D_METHOD("get_collision_mask_value", "layer_number"), &NavigationMesh::get_collision_mask_value); ClassDB::bind_method(D_METHOD("set_source_geometry_mode", "mask"), &NavigationMesh::set_source_geometry_mode); ClassDB::bind_method(D_METHOD("get_source_geometry_mode"), &NavigationMesh::get_source_geometry_mode); @@ -462,14 +475,6 @@ void NavigationMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_polygons", "polygons"), &NavigationMesh::_set_polygons); ClassDB::bind_method(D_METHOD("_get_polygons"), &NavigationMesh::_get_polygons); - BIND_CONSTANT(SAMPLE_PARTITION_WATERSHED); - BIND_CONSTANT(SAMPLE_PARTITION_MONOTONE); - BIND_CONSTANT(SAMPLE_PARTITION_LAYERS); - - BIND_CONSTANT(PARSED_GEOMETRY_MESH_INSTANCES); - BIND_CONSTANT(PARSED_GEOMETRY_STATIC_COLLIDERS); - BIND_CONSTANT(PARSED_GEOMETRY_BOTH); - ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "set_vertices", "get_vertices"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "polygons", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_polygons", "_get_polygons"); @@ -496,6 +501,21 @@ void NavigationMesh::_bind_methods() { 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"); + + BIND_ENUM_CONSTANT(SAMPLE_PARTITION_WATERSHED); + BIND_ENUM_CONSTANT(SAMPLE_PARTITION_MONOTONE); + BIND_ENUM_CONSTANT(SAMPLE_PARTITION_LAYERS); + BIND_ENUM_CONSTANT(SAMPLE_PARTITION_MAX); + + BIND_ENUM_CONSTANT(PARSED_GEOMETRY_MESH_INSTANCES); + BIND_ENUM_CONSTANT(PARSED_GEOMETRY_STATIC_COLLIDERS); + BIND_ENUM_CONSTANT(PARSED_GEOMETRY_BOTH); + BIND_ENUM_CONSTANT(PARSED_GEOMETRY_MAX); + + BIND_ENUM_CONSTANT(SOURCE_GEOMETRY_NAVMESH_CHILDREN); + BIND_ENUM_CONSTANT(SOURCE_GEOMETRY_GROUPS_WITH_CHILDREN); + BIND_ENUM_CONSTANT(SOURCE_GEOMETRY_GROUPS_EXPLICIT); + BIND_ENUM_CONSTANT(SOURCE_GEOMETRY_MAX); } void NavigationMesh::_validate_property(PropertyInfo &property) const { diff --git a/scene/resources/navigation_mesh.h b/scene/resources/navigation_mesh.h index 966221c7c6..1cdf7a07ed 100644 --- a/scene/resources/navigation_mesh.h +++ b/scene/resources/navigation_mesh.h @@ -109,20 +109,20 @@ protected: public: // Recast settings - void set_sample_partition_type(int p_value); - int get_sample_partition_type() const; + void set_sample_partition_type(SamplePartitionType p_value); + SamplePartitionType get_sample_partition_type() const; - void set_parsed_geometry_type(int p_value); - int get_parsed_geometry_type() const; + void set_parsed_geometry_type(ParsedGeometryType p_value); + ParsedGeometryType get_parsed_geometry_type() const; void set_collision_mask(uint32_t p_mask); uint32_t get_collision_mask() const; - void set_collision_mask_bit(int p_bit, bool p_value); - bool get_collision_mask_bit(int p_bit) const; + void set_collision_mask_value(int p_layer_number, bool p_value); + bool get_collision_mask_value(int p_layer_number) const; - void set_source_geometry_mode(int p_geometry_mode); - int get_source_geometry_mode() const; + void set_source_geometry_mode(SourceGeometryMode p_geometry_mode); + SourceGeometryMode get_source_geometry_mode() const; void set_source_group_name(StringName p_group_name); StringName get_source_group_name() const; @@ -190,4 +190,8 @@ public: NavigationMesh(); }; +VARIANT_ENUM_CAST(NavigationMesh::SamplePartitionType); +VARIANT_ENUM_CAST(NavigationMesh::ParsedGeometryType); +VARIANT_ENUM_CAST(NavigationMesh::SourceGeometryMode); + #endif // NAVIGATION_MESH_H diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index 9bb2a4ddb8..e74f759855 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -162,12 +162,14 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { } 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<Node3D>(ret_nodes[n.parent])) { - obj = memnew(Node3D); - } else if (Object::cast_to<Control>(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); +#endif // _3D_DISABLED } } @@ -206,8 +208,8 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { node->set(snames[nprops[j].name], props[nprops[j].value], &valid); //restore old state for new script, if exists - for (List<Pair<StringName, Variant>>::Element *E = old_state.front(); E; E = E->next()) { - node->set(E->get().first, E->get().second); + for (const Pair<StringName, Variant> &E : old_state) { + node->set(E.first, E.second); } } else { Variant value = props[nprops[j].value]; @@ -377,10 +379,17 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map return OK; } + bool is_editable_instance = false; + // save the child instantiated scenes that are chosen as editable, so they can be restored // upon load back if (p_node != p_owner && p_node->get_filename() != String() && p_owner->is_editable_instance(p_node)) { editable_instances.push_back(p_owner->get_path_to(p_node)); + // Node is the root of an editable instance. + is_editable_instance = true; + } else if (p_node->get_owner() && p_node->get_owner() != p_owner && p_owner->is_editable_instance(p_node->get_owner())) { + // Node is part of an editable instance. + is_editable_instance = true; } NodeData nd; @@ -477,13 +486,13 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map script->update_exports(); } - for (List<PropertyInfo>::Element *E = plist.front(); E; E = E->next()) { - if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) { + for (const PropertyInfo &E : plist) { + if (!(E.usage & PROPERTY_USAGE_STORAGE)) { continue; } - String name = E->get().name; - Variant value = p_node->get(E->get().name); + String name = E.name; + Variant value = p_node->get(E.name); bool isdefault = false; Variant default_value = ClassDB::class_get_default_property_value(type, name); @@ -497,7 +506,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map } // the version above makes more sense, because it does not rely on placeholder or usage flag // in the script, just the default value function. - // if (E->get().usage & PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE) { + // if (E.usage & PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE) { // isdefault = true; //is script default value // } @@ -507,7 +516,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map // only save what has been changed // only save changed properties in instance - if ((E->get().usage & PROPERTY_USAGE_NO_INSTANCE_STATE) || E->get().name == "__meta__") { + if ((E.usage & PROPERTY_USAGE_NO_INSTANCE_STATE) || E.name == "__meta__") { //property has requested that no instance state is saved, sorry //also, meta won't be overridden or saved continue; @@ -520,7 +529,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map //check all levels of pack to see if the property exists somewhere const PackState &ps = F->get(); - original = ps.state->get_property_value(ps.node, E->get().name, exists); + original = ps.state->get_property_value(ps.node, E.name, exists); if (exists) { break; } @@ -565,9 +574,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map List<Node::GroupInfo> groups; p_node->get_groups(&groups); - for (List<Node::GroupInfo>::Element *E = groups.front(); E; E = E->next()) { - Node::GroupInfo &gi = E->get(); - + for (const Node::GroupInfo &gi : groups) { if (!gi.persistent) { continue; } @@ -577,9 +584,9 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map */ bool skip = false; - for (List<PackState>::Element *F = pack_state_stack.front(); F; F = F->next()) { + for (const PackState &F : pack_state_stack) { //check all levels of pack to see if the group was added somewhere - const PackState &ps = F->get(); + const PackState &ps = F; if (ps.state->is_node_in_group(ps.node, gi.name)) { skip = true; break; @@ -610,7 +617,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map // Save the right type. If this node was created by an instance // then flag that the node should not be created but reused - if (pack_state_stack.is_empty()) { + if (pack_state_stack.is_empty() && !is_editable_instance) { //this node is not part of an instancing process, so save the type nd.type = _nm_get_string(p_node->get_class(), name_map); } else { @@ -679,14 +686,14 @@ Error SceneState::_parse_connections(Node *p_owner, Node *p_node, Map<StringName //ERR_FAIL_COND_V( !node_map.has(p_node), ERR_BUG); //NodeData &nd = nodes[node_map[p_node]]; - for (List<MethodInfo>::Element *E = _signals.front(); E; E = E->next()) { + for (const MethodInfo &E : _signals) { List<Node::Connection> conns; - p_node->get_signal_connection_list(E->get().name, &conns); + p_node->get_signal_connection_list(E.name, &conns); conns.sort(); - for (List<Node::Connection>::Element *F = conns.front(); F; F = F->next()) { - const Node::Connection &c = F->get(); + for (const Node::Connection &F : conns) { + const Node::Connection &c = F; if (!(c.flags & CONNECT_PERSIST)) { //only persistent connections get saved continue; diff --git a/scene/resources/particles_material.cpp b/scene/resources/particles_material.cpp index e95df31ccc..0495a9e92c 100644 --- a/scene/resources/particles_material.cpp +++ b/scene/resources/particles_material.cpp @@ -30,6 +30,8 @@ #include "particles_material.h" +#include "core/version.h" + Mutex ParticlesMaterial::material_mutex; SelfList<ParticlesMaterial>::List *ParticlesMaterial::dirty_materials = nullptr; Map<ParticlesMaterial::MaterialKey, ParticlesMaterial::ShaderData> ParticlesMaterial::shader_map; @@ -43,31 +45,31 @@ void ParticlesMaterial::init_shaders() { shader_names->direction = "direction"; shader_names->spread = "spread"; shader_names->flatness = "flatness"; - shader_names->initial_linear_velocity = "initial_linear_velocity"; - shader_names->initial_angle = "initial_angle"; - shader_names->angular_velocity = "angular_velocity"; - shader_names->orbit_velocity = "orbit_velocity"; - shader_names->linear_accel = "linear_accel"; - shader_names->radial_accel = "radial_accel"; - shader_names->tangent_accel = "tangent_accel"; - shader_names->damping = "damping"; - shader_names->scale = "scale"; - shader_names->hue_variation = "hue_variation"; - shader_names->anim_speed = "anim_speed"; - shader_names->anim_offset = "anim_offset"; - - shader_names->initial_linear_velocity_random = "initial_linear_velocity_random"; - shader_names->initial_angle_random = "initial_angle_random"; - shader_names->angular_velocity_random = "angular_velocity_random"; - shader_names->orbit_velocity_random = "orbit_velocity_random"; - shader_names->linear_accel_random = "linear_accel_random"; - shader_names->radial_accel_random = "radial_accel_random"; - shader_names->tangent_accel_random = "tangent_accel_random"; - shader_names->damping_random = "damping_random"; - shader_names->scale_random = "scale_random"; - shader_names->hue_variation_random = "hue_variation_random"; - shader_names->anim_speed_random = "anim_speed_random"; - shader_names->anim_offset_random = "anim_offset_random"; + shader_names->initial_linear_velocity_min = "initial_linear_velocity_min"; + shader_names->initial_angle_min = "initial_angle_min"; + shader_names->angular_velocity_min = "angular_velocity_min"; + shader_names->orbit_velocity_min = "orbit_velocity_min"; + shader_names->linear_accel_min = "linear_accel_min"; + shader_names->radial_accel_min = "radial_accel_min"; + shader_names->tangent_accel_min = "tangent_accel_min"; + shader_names->damping_min = "damping_min"; + shader_names->scale_min = "scale_min"; + shader_names->hue_variation_min = "hue_variation_min"; + shader_names->anim_speed_min = "anim_speed_min"; + shader_names->anim_offset_min = "anim_offset_min"; + + shader_names->initial_linear_velocity_max = "initial_linear_velocity_max"; + shader_names->initial_angle_max = "initial_angle_max"; + shader_names->angular_velocity_max = "angular_velocity_max"; + shader_names->orbit_velocity_max = "orbit_velocity_max"; + shader_names->linear_accel_max = "linear_accel_max"; + shader_names->radial_accel_max = "radial_accel_max"; + shader_names->tangent_accel_max = "tangent_accel_max"; + shader_names->damping_max = "damping_max"; + shader_names->scale_max = "scale_max"; + shader_names->hue_variation_max = "hue_variation_max"; + shader_names->anim_speed_max = "anim_speed_max"; + shader_names->anim_offset_max = "anim_offset_max"; shader_names->angle_texture = "angle_texture"; shader_names->angular_velocity_texture = "angular_velocity_texture"; @@ -90,6 +92,10 @@ void ParticlesMaterial::init_shaders() { shader_names->emission_texture_points = "emission_texture_points"; shader_names->emission_texture_normal = "emission_texture_normal"; shader_names->emission_texture_color = "emission_texture_color"; + shader_names->emission_ring_axis = "emission_ring_axis"; + shader_names->emission_ring_height = "emission_ring_height"; + shader_names->emission_ring_radius = "emission_ring_radius"; + shader_names->emission_ring_inner_radius = "emission_ring_inner_radius"; shader_names->gravity = "gravity"; @@ -137,7 +143,10 @@ void ParticlesMaterial::_update_shader() { //must create a shader! - String code = "shader_type particles;\n"; + // Add a comment to describe the shader origin (useful when converting to ShaderMaterial). + String code = "// NOTE: Shader automatically converted from " VERSION_NAME " " VERSION_FULL_CONFIG "'s ParticlesMaterial.\n\n"; + + code += "shader_type particles;\n"; if (collision_scale) { code += "render_mode collision_use_scale;\n"; @@ -146,31 +155,31 @@ void ParticlesMaterial::_update_shader() { code += "uniform vec3 direction;\n"; code += "uniform float spread;\n"; code += "uniform float flatness;\n"; - code += "uniform float initial_linear_velocity;\n"; - code += "uniform float initial_angle;\n"; - code += "uniform float angular_velocity;\n"; - code += "uniform float orbit_velocity;\n"; - code += "uniform float linear_accel;\n"; - code += "uniform float radial_accel;\n"; - code += "uniform float tangent_accel;\n"; - code += "uniform float damping;\n"; - code += "uniform float scale;\n"; - code += "uniform float hue_variation;\n"; - code += "uniform float anim_speed;\n"; - code += "uniform float anim_offset;\n"; - - code += "uniform float initial_linear_velocity_random;\n"; - code += "uniform float initial_angle_random;\n"; - code += "uniform float angular_velocity_random;\n"; - code += "uniform float orbit_velocity_random;\n"; - code += "uniform float linear_accel_random;\n"; - code += "uniform float radial_accel_random;\n"; - code += "uniform float tangent_accel_random;\n"; - code += "uniform float damping_random;\n"; - code += "uniform float scale_random;\n"; - code += "uniform float hue_variation_random;\n"; - code += "uniform float anim_speed_random;\n"; - code += "uniform float anim_offset_random;\n"; + code += "uniform float initial_linear_velocity_min;\n"; + code += "uniform float initial_angle_min;\n"; + code += "uniform float angular_velocity_min;\n"; + code += "uniform float orbit_velocity_min;\n"; + code += "uniform float linear_accel_min;\n"; + code += "uniform float radial_accel_min;\n"; + code += "uniform float tangent_accel_min;\n"; + code += "uniform float damping_min;\n"; + code += "uniform float scale_min;\n"; + code += "uniform float hue_variation_min;\n"; + code += "uniform float anim_speed_min;\n"; + code += "uniform float anim_offset_min;\n"; + + code += "uniform float initial_linear_velocity_max;\n"; + code += "uniform float initial_angle_max;\n"; + code += "uniform float angular_velocity_max;\n"; + code += "uniform float orbit_velocity_max;\n"; + code += "uniform float linear_accel_max;\n"; + code += "uniform float radial_accel_max;\n"; + code += "uniform float tangent_accel_max;\n"; + code += "uniform float damping_max;\n"; + code += "uniform float scale_max;\n"; + code += "uniform float hue_variation_max;\n"; + code += "uniform float anim_speed_max;\n"; + code += "uniform float anim_offset_max;\n"; code += "uniform float lifetime_randomness;\n"; switch (emission_shape) { @@ -194,6 +203,12 @@ void ParticlesMaterial::_update_shader() { code += "uniform sampler2D emission_texture_color : hint_white;\n"; } } break; + case EMISSION_SHAPE_RING: { + code += "uniform vec3 " + shader_names->emission_ring_axis + ";\n"; + code += "uniform float " + shader_names->emission_ring_height + ";\n"; + code += "uniform float " + shader_names->emission_ring_radius + ";\n"; + code += "uniform float " + shader_names->emission_ring_inner_radius + ";\n"; + } break; case EMISSION_SHAPE_MAX: { // Max value for validity check. break; } @@ -314,7 +329,7 @@ void ParticlesMaterial::_update_shader() { if (tex_parameters[PARAM_ANIM_OFFSET].is_valid()) { code += " float tex_anim_offset = textureLod(anim_offset_texture, vec2(0.0, 0.0), 0.0).r;\n"; } else { - code += " float tex_anim_offset = 0.0;\n"; + code += " float tex_anim_offset = 1.0;\n"; } code += " float spread_rad = spread * degree_to_rad;\n"; @@ -324,42 +339,46 @@ void ParticlesMaterial::_update_shader() { if (tex_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { code += " float tex_linear_velocity = textureLod(linear_velocity_texture, vec2(0.0, 0.0), 0.0).r;\n"; } else { - code += " float tex_linear_velocity = 0.0;\n"; + code += " float tex_linear_velocity = 1.0;\n"; } if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { - code += " float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n"; - code += " angle1_rad += direction.x != 0.0 ? atan(direction.y, direction.x) : sign(direction.y) * (pi / 2.0);\n"; - code += " vec3 rot = vec3(cos(angle1_rad), sin(angle1_rad), 0.0);\n"; - code += " VELOCITY = rot * initial_linear_velocity * mix(1.0, rand_from_seed(alt_seed), initial_linear_velocity_random);\n"; + code += " {\n"; + code += " float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n"; + code += " angle1_rad += direction.x != 0.0 ? atan(direction.y, direction.x) : sign(direction.y) * (pi / 2.0);\n"; + code += " vec3 rot = vec3(cos(angle1_rad), sin(angle1_rad), 0.0);\n"; + code += " VELOCITY = rot * mix(initial_linear_velocity_min,initial_linear_velocity_max, rand_from_seed(alt_seed));\n"; + code += " }\n"; } else { //initiate velocity spread in 3D - code += " float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n"; - code += " float angle2_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad * (1.0 - flatness);\n"; - code += " vec3 direction_xz = vec3(sin(angle1_rad), 0.0, cos(angle1_rad));\n"; - code += " vec3 direction_yz = vec3(0.0, sin(angle2_rad), cos(angle2_rad));\n"; - code += " direction_yz.z = direction_yz.z / max(0.0001,sqrt(abs(direction_yz.z))); // better uniform distribution\n"; - code += " vec3 spread_direction = vec3(direction_xz.x * direction_yz.z, direction_yz.y, direction_xz.z * direction_yz.z);\n"; - code += " vec3 direction_nrm = normalize(direction);\n"; - code += " // rotate spread to direction\n"; - code += " vec3 binormal = cross(vec3(0.0, 1.0, 0.0), direction_nrm);\n"; - code += " if (length(binormal) < 0.0001) {\n"; - code += " // direction is parallel to Y. Choose Z as the binormal.\n"; - code += " binormal = vec3(0.0, 0.0, 1.0);\n"; + code += " {\n"; + code += " float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n"; + code += " float angle2_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad * (1.0 - flatness);\n"; + code += " vec3 direction_xz = vec3(sin(angle1_rad), 0.0, cos(angle1_rad));\n"; + code += " vec3 direction_yz = vec3(0.0, sin(angle2_rad), cos(angle2_rad));\n"; + code += " direction_yz.z = direction_yz.z / max(0.0001,sqrt(abs(direction_yz.z))); // better uniform distribution\n"; + code += " vec3 spread_direction = vec3(direction_xz.x * direction_yz.z, direction_yz.y, direction_xz.z * direction_yz.z);\n"; + code += " vec3 direction_nrm = length(direction) > 0.0 ? normalize(direction) : vec3(0.0, 0.0, 1.0);\n"; + code += " // rotate spread to direction\n"; + code += " vec3 binormal = cross(vec3(0.0, 1.0, 0.0), direction_nrm);\n"; + code += " if (length(binormal) < 0.0001) {\n"; + code += " // direction is parallel to Y. Choose Z as the binormal.\n"; + code += " binormal = vec3(0.0, 0.0, 1.0);\n"; + code += " }\n"; + code += " binormal = normalize(binormal);\n"; + code += " vec3 normal = cross(binormal, direction_nrm);\n"; + code += " spread_direction = binormal * spread_direction.x + normal * spread_direction.y + direction_nrm * spread_direction.z;\n"; + code += " VELOCITY = spread_direction * mix(initial_linear_velocity_min, initial_linear_velocity_max,rand_from_seed(alt_seed));\n"; code += " }\n"; - code += " binormal = normalize(binormal);\n"; - code += " vec3 normal = cross(binormal, direction_nrm);\n"; - code += " spread_direction = binormal * spread_direction.x + normal * spread_direction.y + direction_nrm * spread_direction.z;\n"; - code += " VELOCITY = spread_direction * initial_linear_velocity * mix(1.0, rand_from_seed(alt_seed), initial_linear_velocity_random);\n"; } code += " }\n"; - code += " float base_angle = (initial_angle + tex_angle) * mix(1.0, angle_rand, initial_angle_random);\n"; + code += " float base_angle = (tex_angle) * mix(initial_angle_min, initial_angle_max, angle_rand);\n"; code += " CUSTOM.x = base_angle * degree_to_rad;\n"; // angle code += " CUSTOM.y = 0.0;\n"; // phase code += " CUSTOM.w = (1.0 - lifetime_randomness * rand_from_seed(alt_seed));\n"; - code += " CUSTOM.z = (anim_offset + tex_anim_offset) * mix(1.0, anim_offset_rand, anim_offset_random);\n"; // animation offset (0-1) + code += " CUSTOM.z = (tex_anim_offset) * mix(anim_offset_min, anim_offset_max, anim_offset_rand);\n"; // animation offset (0-1) code += " if (RESTART_POSITION) {\n"; @@ -383,19 +402,45 @@ void ParticlesMaterial::_update_shader() { if (emission_shape == EMISSION_SHAPE_DIRECTED_POINTS) { if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { - code += " mat2 rotm;"; - code += " rotm[0] = texelFetch(emission_texture_normal, emission_tex_ofs, 0).xy;\n"; - code += " rotm[1] = rotm[0].yx * vec2(1.0, -1.0);\n"; - code += " if (RESTART_VELOCITY) VELOCITY.xy = rotm * VELOCITY.xy;\n"; + code += " {\n"; + code += " mat2 rotm;"; + code += " rotm[0] = texelFetch(emission_texture_normal, emission_tex_ofs, 0).xy;\n"; + code += " rotm[1] = rotm[0].yx * vec2(1.0, -1.0);\n"; + code += " if (RESTART_VELOCITY) VELOCITY.xy = rotm * VELOCITY.xy;\n"; + code += " }\n"; } else { - code += " vec3 normal = texelFetch(emission_texture_normal, emission_tex_ofs, 0).xyz;\n"; - code += " vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);\n"; - code += " vec3 tangent = normalize(cross(v0, normal));\n"; - code += " vec3 bitangent = normalize(cross(tangent, normal));\n"; - code += " if (RESTART_VELOCITY) VELOCITY = mat3(tangent, bitangent, normal) * VELOCITY;\n"; + code += " {\n"; + code += " vec3 normal = texelFetch(emission_texture_normal, emission_tex_ofs, 0).xyz;\n"; + code += " vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);\n"; + code += " vec3 tangent = normalize(cross(v0, normal));\n"; + code += " vec3 bitangent = normalize(cross(tangent, normal));\n"; + code += " if (RESTART_VELOCITY) VELOCITY = mat3(tangent, bitangent, normal) * VELOCITY;\n"; + code += " }\n"; } } } break; + case EMISSION_SHAPE_RING: { + code += " float ring_spawn_angle = rand_from_seed(alt_seed) * 2.0 * pi;\n"; + code += " float ring_random_radius = rand_from_seed(alt_seed) * (emission_ring_radius - emission_ring_inner_radius) + emission_ring_inner_radius;\n"; + code += " vec3 axis = normalize(emission_ring_axis);\n"; + code += " vec3 ortho_axis = vec3(0.0);\n"; + code += " if (axis == vec3(1.0, 0.0, 0.0)) {\n"; + code += " ortho_axis = cross(axis, vec3(0.0, 1.0, 0.0));\n"; + code += " } else {\n"; + code += " ortho_axis = cross(axis, vec3(1.0, 0.0, 0.0));\n"; + code += " }\n"; + code += " ortho_axis = normalize(ortho_axis);\n"; + code += " float s = sin(ring_spawn_angle);\n"; + code += " float c = cos(ring_spawn_angle);\n"; + code += " float oc = 1.0 - c;\n"; + code += " ortho_axis = mat3(\n"; + code += " vec3(c + axis.x * axis.x * oc, axis.x * axis.y * oc - axis.z * s, axis.x * axis.z *oc + axis.y * s),\n"; + code += " vec3(axis.x * axis.y * oc + s * axis.z, c + axis.y * axis.y * oc, axis.y * axis.z * oc - axis.x * s),\n"; + code += " vec3(axis.z * axis.x * oc - axis.y * s, axis.z * axis.y * oc + axis.x * s, c + axis.z * axis.z * oc)\n"; + code += " ) * ortho_axis;\n"; + code += " ortho_axis = normalize(ortho_axis);\n"; + code += " TRANSFORM[3].xyz = ortho_axis * ring_random_radius + (rand_from_seed(alt_seed) * emission_ring_height - emission_ring_height / 2.0) * axis;\n"; + } break; case EMISSION_SHAPE_MAX: { // Max value for validity check. break; } @@ -426,63 +471,63 @@ void ParticlesMaterial::_update_shader() { if (tex_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { code += " float tex_linear_velocity = textureLod(linear_velocity_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_linear_velocity = 0.0;\n"; + code += " float tex_linear_velocity = 1.0;\n"; } if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { if (tex_parameters[PARAM_ORBIT_VELOCITY].is_valid()) { code += " float tex_orbit_velocity = textureLod(orbit_velocity_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_orbit_velocity = 0.0;\n"; + code += " float tex_orbit_velocity = 1.0;\n"; } } if (tex_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) { code += " float tex_angular_velocity = textureLod(angular_velocity_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_angular_velocity = 0.0;\n"; + code += " float tex_angular_velocity = 1.0;\n"; } if (tex_parameters[PARAM_LINEAR_ACCEL].is_valid()) { code += " float tex_linear_accel = textureLod(linear_accel_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_linear_accel = 0.0;\n"; + code += " float tex_linear_accel = 1.0;\n"; } if (tex_parameters[PARAM_RADIAL_ACCEL].is_valid()) { code += " float tex_radial_accel = textureLod(radial_accel_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_radial_accel = 0.0;\n"; + code += " float tex_radial_accel = 1.0;\n"; } if (tex_parameters[PARAM_TANGENTIAL_ACCEL].is_valid()) { code += " float tex_tangent_accel = textureLod(tangent_accel_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_tangent_accel = 0.0;\n"; + code += " float tex_tangent_accel = 1.0;\n"; } if (tex_parameters[PARAM_DAMPING].is_valid()) { code += " float tex_damping = textureLod(damping_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_damping = 0.0;\n"; + code += " float tex_damping = 1.0;\n"; } if (tex_parameters[PARAM_ANGLE].is_valid()) { code += " float tex_angle = textureLod(angle_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_angle = 0.0;\n"; + code += " float tex_angle = 1.0;\n"; } if (tex_parameters[PARAM_ANIM_SPEED].is_valid()) { code += " float tex_anim_speed = textureLod(anim_speed_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_anim_speed = 0.0;\n"; + code += " float tex_anim_speed = 1.0;\n"; } if (tex_parameters[PARAM_ANIM_OFFSET].is_valid()) { code += " float tex_anim_offset = textureLod(anim_offset_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_anim_offset = 0.0;\n"; + code += " float tex_anim_offset = 1.0;\n"; } code += " vec3 force = gravity;\n"; @@ -491,18 +536,19 @@ void ParticlesMaterial::_update_shader() { code += " pos.z = 0.0;\n"; } code += " // apply linear acceleration\n"; - code += " force += length(VELOCITY) > 0.0 ? normalize(VELOCITY) * (linear_accel + tex_linear_accel) * mix(1.0, rand_from_seed(alt_seed), linear_accel_random) : vec3(0.0);\n"; + code += " force += length(VELOCITY) > 0.0 ? normalize(VELOCITY) * tex_linear_accel * mix(linear_accel_min, linear_accel_max, rand_from_seed(alt_seed)) : vec3(0.0);\n"; code += " // apply radial acceleration\n"; code += " vec3 org = EMISSION_TRANSFORM[3].xyz;\n"; code += " vec3 diff = pos - org;\n"; - code += " force += length(diff) > 0.0 ? normalize(diff) * (radial_accel + tex_radial_accel) * mix(1.0, rand_from_seed(alt_seed), radial_accel_random) : vec3(0.0);\n"; + code += " force += length(diff) > 0.0 ? normalize(diff) * tex_radial_accel * mix(radial_accel_min, radial_accel_max, rand_from_seed(alt_seed)) : vec3(0.0);\n"; code += " // apply tangential acceleration;\n"; + code += " float tangent_accel_val = tex_tangent_accel * mix(tangent_accel_min, tangent_accel_max, rand_from_seed(alt_seed))\n;"; if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { - code += " force += length(diff.yx) > 0.0 ? vec3(normalize(diff.yx * vec2(-1.0, 1.0)), 0.0) * ((tangent_accel + tex_tangent_accel) * mix(1.0, rand_from_seed(alt_seed), tangent_accel_random)) : vec3(0.0);\n"; + code += " force += length(diff.yx) > 0.0 ? vec3(normalize(diff.yx * vec2(-1.0, 1.0)), 0.0) * tangent_accel_val : vec3(0.0);\n"; } else { code += " vec3 crossDiff = cross(normalize(diff), normalize(gravity));\n"; - code += " force += length(crossDiff) > 0.0 ? normalize(crossDiff) * ((tangent_accel + tex_tangent_accel) * mix(1.0, rand_from_seed(alt_seed), tangent_accel_random)) : vec3(0.0);\n"; + code += " force += length(crossDiff) > 0.0 ? normalize(crossDiff) * tangent_accel_val : vec3(0.0);\n"; } if (attractor_interaction_enabled) { code += " force += ATTRACTOR_FORCE;\n\n"; @@ -512,7 +558,7 @@ void ParticlesMaterial::_update_shader() { code += " VELOCITY += force * DELTA;\n"; code += " // orbit velocity\n"; if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { - code += " float orbit_amount = (orbit_velocity + tex_orbit_velocity) * mix(1.0, rand_from_seed(alt_seed), orbit_velocity_random);\n"; + code += " float orbit_amount = tex_orbit_velocity * mix(orbit_velocity_min, orbit_velocity_max, rand_from_seed(alt_seed));\n"; code += " if (orbit_amount != 0.0) {\n"; code += " float ang = orbit_amount * DELTA * pi * 2.0;\n"; code += " mat2 rot = mat2(vec2(cos(ang), -sin(ang)), vec2(sin(ang), cos(ang)));\n"; @@ -524,9 +570,10 @@ void ParticlesMaterial::_update_shader() { if (tex_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { code += " VELOCITY = normalize(VELOCITY) * tex_linear_velocity;\n"; } - code += " if (damping + tex_damping > 0.0) {\n"; + code += " float dmp = mix(damping_min, damping_max, rand_from_seed(alt_seed));\n"; + code += " if (dmp * tex_damping > 0.0) {\n"; code += " float v = length(VELOCITY);\n"; - code += " float damp = (damping + tex_damping) * mix(1.0, rand_from_seed(alt_seed), damping_random);\n"; + code += " float damp = tex_damping * dmp;\n"; code += " v -= damp * DELTA;\n"; code += " if (v < 0.0) {\n"; code += " VELOCITY = vec3(0.0);\n"; @@ -534,26 +581,26 @@ void ParticlesMaterial::_update_shader() { code += " VELOCITY = normalize(VELOCITY) * v;\n"; code += " }\n"; code += " }\n"; - code += " float base_angle = (initial_angle + tex_angle) * mix(1.0, angle_rand, initial_angle_random);\n"; - code += " base_angle += CUSTOM.y * LIFETIME * (angular_velocity + tex_angular_velocity) * mix(1.0, rand_from_seed(alt_seed) * 2.0 - 1.0, angular_velocity_random);\n"; + code += " float base_angle = (tex_angle) * mix(initial_angle_min, initial_angle_max, rand_from_seed(alt_seed));\n"; + code += " base_angle += CUSTOM.y * LIFETIME * (tex_angular_velocity) * mix(angular_velocity_min,angular_velocity_max, rand_from_seed(alt_seed));\n"; code += " CUSTOM.x = base_angle * degree_to_rad;\n"; // angle - code += " CUSTOM.z = (anim_offset + tex_anim_offset) * mix(1.0, anim_offset_rand, anim_offset_random) + CUSTOM.y * (anim_speed + tex_anim_speed) * mix(1.0, rand_from_seed(alt_seed), anim_speed_random);\n"; // angle + code += " CUSTOM.z = (tex_anim_offset) * mix(anim_offset_min, anim_offset_max, rand_from_seed(alt_seed)) + CUSTOM.y * tex_anim_speed * mix(anim_speed_min, anim_speed_max, rand_from_seed(alt_seed));\n"; // angle // apply color // apply hue rotation if (tex_parameters[PARAM_SCALE].is_valid()) { - code += " float tex_scale = textureLod(scale_texture, vec2(tv, 0.0), 0.0).r;\n"; + code += " vec3 tex_scale = textureLod(scale_texture, vec2(tv, 0.0), 0.0).rgb;\n"; } else { - code += " float tex_scale = 1.0;\n"; + code += " vec3 tex_scale = vec3(1.0);\n"; } if (tex_parameters[PARAM_HUE_VARIATION].is_valid()) { code += " float tex_hue_variation = textureLod(hue_variation_texture, vec2(tv, 0.0), 0.0).r;\n"; } else { - code += " float tex_hue_variation = 0.0;\n"; + code += " float tex_hue_variation = 1.0;\n"; } - code += " float hue_rot_angle = (hue_variation + tex_hue_variation) * pi * 2.0 * mix(1.0, hue_rot_rand * 2.0 - 1.0, hue_variation_random);\n"; + code += " float hue_rot_angle = (tex_hue_variation) * pi * 2.0 * mix(hue_variation_min, hue_variation_max, rand_from_seed(alt_seed));\n"; code += " float hue_rot_c = cos(hue_rot_angle);\n"; code += " float hue_rot_s = sin(hue_rot_angle);\n"; code += " mat4 hue_rot_mat = mat4(vec4(0.299, 0.587, 0.114, 0.0),\n"; @@ -569,7 +616,7 @@ void ParticlesMaterial::_update_shader() { code += " vec4(1.250, -1.050, -0.203, 0.0),\n"; code += " vec4(0.000, 0.000, 0.000, 0.0)) * hue_rot_s;\n"; if (color_ramp.is_valid()) { - code += " COLOR = hue_rot_mat * textureLod(color_ramp, vec2(tv, 0.0), 0.0);\n"; + code += " COLOR = hue_rot_mat * textureLod(color_ramp, vec2(tv, 0.0), 0.0) * color_value;\n"; } else { code += " COLOR = hue_rot_mat * color_value;\n"; } @@ -615,18 +662,18 @@ void ParticlesMaterial::_update_shader() { } // turn particle by rotation in Y if (particle_flags[PARTICLE_FLAG_ROTATE_Y]) { + code += " vec4 origin = TRANSFORM[3];\n"; code += " TRANSFORM = mat4(vec4(cos(CUSTOM.x), 0.0, -sin(CUSTOM.x), 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(sin(CUSTOM.x), 0.0, cos(CUSTOM.x), 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; + code += " TRANSFORM[3] = origin;\n"; } } //scale by scale - code += " float base_scale = tex_scale * mix(scale, 1.0, scale_random * scale_rand);\n"; - code += " if (base_scale < 0.000001) {\n"; - code += " base_scale = 0.000001;\n"; - code += " }\n"; + code += " float base_scale = mix(scale_min, scale_max, scale_rand);\n"; + code += " base_scale = sign(base_scale) * max(abs(base_scale), 0.001);\n"; - code += " TRANSFORM[0].xyz *= base_scale;\n"; - code += " TRANSFORM[1].xyz *= base_scale;\n"; - code += " TRANSFORM[2].xyz *= base_scale;\n"; + code += " TRANSFORM[0].xyz *= base_scale * sign(tex_scale.r) * max(abs(tex_scale.r), 0.001);\n"; + code += " TRANSFORM[1].xyz *= base_scale * sign(tex_scale.g) * max(abs(tex_scale.g), 0.001);\n"; + code += " TRANSFORM[2].xyz *= base_scale * sign(tex_scale.b) * max(abs(tex_scale.b), 0.001);\n"; if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { code += " VELOCITY.z = 0.0;\n"; code += " TRANSFORM[3].z = 0.0;\n"; @@ -732,110 +779,116 @@ float ParticlesMaterial::get_flatness() const { return flatness; } -void ParticlesMaterial::set_param(Parameter p_param, float p_value) { +void ParticlesMaterial::set_param_min(Parameter p_param, float p_value) { ERR_FAIL_INDEX(p_param, PARAM_MAX); - parameters[p_param] = p_value; + params_min[p_param] = p_value; + if (params_min[p_param] > params_max[p_param]) { + set_param_max(p_param, p_value); + } switch (p_param) { case PARAM_INITIAL_LINEAR_VELOCITY: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->initial_linear_velocity, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->initial_linear_velocity_min, p_value); } break; case PARAM_ANGULAR_VELOCITY: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->angular_velocity, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->angular_velocity_min, p_value); } break; case PARAM_ORBIT_VELOCITY: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->orbit_velocity, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->orbit_velocity_min, p_value); } break; case PARAM_LINEAR_ACCEL: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->linear_accel, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->linear_accel_min, p_value); } break; case PARAM_RADIAL_ACCEL: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->radial_accel, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->radial_accel_min, p_value); } break; case PARAM_TANGENTIAL_ACCEL: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->tangent_accel, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->tangent_accel_min, p_value); } break; case PARAM_DAMPING: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->damping, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->damping_min, p_value); } break; case PARAM_ANGLE: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->initial_angle, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->initial_angle_min, p_value); } break; case PARAM_SCALE: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->scale, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->scale_min, p_value); } break; case PARAM_HUE_VARIATION: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->hue_variation, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->hue_variation_min, p_value); } break; case PARAM_ANIM_SPEED: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_speed, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_speed_min, p_value); } break; case PARAM_ANIM_OFFSET: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_offset, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_offset_min, p_value); } break; case PARAM_MAX: break; // Can't happen, but silences warning } } -float ParticlesMaterial::get_param(Parameter p_param) const { +float ParticlesMaterial::get_param_min(Parameter p_param) const { ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0); - return parameters[p_param]; + return params_min[p_param]; } -void ParticlesMaterial::set_param_randomness(Parameter p_param, float p_value) { +void ParticlesMaterial::set_param_max(Parameter p_param, float p_value) { ERR_FAIL_INDEX(p_param, PARAM_MAX); - randomness[p_param] = p_value; + params_max[p_param] = p_value; + if (params_min[p_param] > params_max[p_param]) { + set_param_min(p_param, p_value); + } switch (p_param) { case PARAM_INITIAL_LINEAR_VELOCITY: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->initial_linear_velocity_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->initial_linear_velocity_max, p_value); } break; case PARAM_ANGULAR_VELOCITY: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->angular_velocity_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->angular_velocity_max, p_value); } break; case PARAM_ORBIT_VELOCITY: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->orbit_velocity_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->orbit_velocity_max, p_value); } break; case PARAM_LINEAR_ACCEL: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->linear_accel_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->linear_accel_max, p_value); } break; case PARAM_RADIAL_ACCEL: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->radial_accel_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->radial_accel_max, p_value); } break; case PARAM_TANGENTIAL_ACCEL: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->tangent_accel_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->tangent_accel_max, p_value); } break; case PARAM_DAMPING: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->damping_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->damping_max, p_value); } break; case PARAM_ANGLE: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->initial_angle_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->initial_angle_max, p_value); } break; case PARAM_SCALE: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->scale_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->scale_max, p_value); } break; case PARAM_HUE_VARIATION: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->hue_variation_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->hue_variation_max, p_value); } break; case PARAM_ANIM_SPEED: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_speed_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_speed_max, p_value); } break; case PARAM_ANIM_OFFSET: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_offset_random, p_value); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_offset_max, p_value); } break; case PARAM_MAX: break; // Can't happen, but silences warning } } -float ParticlesMaterial::get_param_randomness(Parameter p_param) const { +float ParticlesMaterial::get_param_max(Parameter p_param) const { ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0); - return randomness[p_param]; + return params_max[p_param]; } static void _adjust_curve_range(const Ref<Texture2D> &p_texture, float p_min, float p_max) { @@ -852,52 +905,54 @@ void ParticlesMaterial::set_param_texture(Parameter p_param, const Ref<Texture2D tex_parameters[p_param] = p_texture; + RID tex_rid = p_texture.is_valid() ? p_texture->get_rid() : RID(); + switch (p_param) { case PARAM_INITIAL_LINEAR_VELOCITY: { //do none for this one } break; case PARAM_ANGULAR_VELOCITY: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->angular_velocity_texture, p_texture); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->angular_velocity_texture, tex_rid); _adjust_curve_range(p_texture, -360, 360); } break; case PARAM_ORBIT_VELOCITY: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->orbit_velocity_texture, p_texture); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->orbit_velocity_texture, tex_rid); _adjust_curve_range(p_texture, -500, 500); } break; case PARAM_LINEAR_ACCEL: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->linear_accel_texture, p_texture); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->linear_accel_texture, tex_rid); _adjust_curve_range(p_texture, -200, 200); } break; case PARAM_RADIAL_ACCEL: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->radial_accel_texture, p_texture); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->radial_accel_texture, tex_rid); _adjust_curve_range(p_texture, -200, 200); } break; case PARAM_TANGENTIAL_ACCEL: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->tangent_accel_texture, p_texture); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->tangent_accel_texture, tex_rid); _adjust_curve_range(p_texture, -200, 200); } break; case PARAM_DAMPING: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->damping_texture, p_texture); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->damping_texture, tex_rid); _adjust_curve_range(p_texture, 0, 100); } break; case PARAM_ANGLE: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->angle_texture, p_texture); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->angle_texture, tex_rid); _adjust_curve_range(p_texture, -360, 360); } break; case PARAM_SCALE: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->scale_texture, p_texture); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->scale_texture, tex_rid); _adjust_curve_range(p_texture, 0, 1); } break; case PARAM_HUE_VARIATION: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->hue_variation_texture, p_texture); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->hue_variation_texture, tex_rid); _adjust_curve_range(p_texture, -1, 1); } break; case PARAM_ANIM_SPEED: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_speed_texture, p_texture); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_speed_texture, tex_rid); _adjust_curve_range(p_texture, 0, 200); } break; case PARAM_ANIM_OFFSET: { - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_offset_texture, p_texture); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_offset_texture, tex_rid); } break; case PARAM_MAX: break; // Can't happen, but silences warning @@ -923,7 +978,8 @@ Color ParticlesMaterial::get_color() const { void ParticlesMaterial::set_color_ramp(const Ref<Texture2D> &p_texture) { color_ramp = p_texture; - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->color_ramp, p_texture); + RID tex_rid = p_texture.is_valid() ? p_texture->get_rid() : RID(); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->color_ramp, tex_rid); _queue_shader_change(); notify_property_list_changed(); } @@ -953,7 +1009,7 @@ void ParticlesMaterial::set_emission_shape(EmissionShape p_shape) { _queue_shader_change(); } -void ParticlesMaterial::set_emission_sphere_radius(float p_radius) { +void ParticlesMaterial::set_emission_sphere_radius(real_t p_radius) { emission_sphere_radius = p_radius; RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_sphere_radius, p_radius); } @@ -965,17 +1021,20 @@ void ParticlesMaterial::set_emission_box_extents(Vector3 p_extents) { void ParticlesMaterial::set_emission_point_texture(const Ref<Texture2D> &p_points) { emission_point_texture = p_points; - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_texture_points, p_points); + RID tex_rid = p_points.is_valid() ? p_points->get_rid() : RID(); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_texture_points, tex_rid); } void ParticlesMaterial::set_emission_normal_texture(const Ref<Texture2D> &p_normals) { emission_normal_texture = p_normals; - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_texture_normal, p_normals); + RID tex_rid = p_normals.is_valid() ? p_normals->get_rid() : RID(); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_texture_normal, tex_rid); } void ParticlesMaterial::set_emission_color_texture(const Ref<Texture2D> &p_colors) { emission_color_texture = p_colors; - RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_texture_color, p_colors); + RID tex_rid = p_colors.is_valid() ? p_colors->get_rid() : RID(); + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_texture_color, tex_rid); _queue_shader_change(); } @@ -984,11 +1043,31 @@ void ParticlesMaterial::set_emission_point_count(int p_count) { RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_texture_point_count, p_count); } +void ParticlesMaterial::set_emission_ring_axis(Vector3 p_axis) { + emission_ring_axis = p_axis; + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_ring_axis, p_axis); +} + +void ParticlesMaterial::set_emission_ring_height(real_t p_height) { + emission_ring_height = p_height; + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_ring_height, p_height); +} + +void ParticlesMaterial::set_emission_ring_radius(real_t p_radius) { + emission_ring_radius = p_radius; + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_ring_radius, p_radius); +} + +void ParticlesMaterial::set_emission_ring_inner_radius(real_t p_radius) { + emission_ring_inner_radius = p_radius; + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_ring_inner_radius, p_radius); +} + ParticlesMaterial::EmissionShape ParticlesMaterial::get_emission_shape() const { return emission_shape; } -float ParticlesMaterial::get_emission_sphere_radius() const { +real_t ParticlesMaterial::get_emission_sphere_radius() const { return emission_sphere_radius; } @@ -1012,6 +1091,22 @@ int ParticlesMaterial::get_emission_point_count() const { return emission_point_count; } +Vector3 ParticlesMaterial::get_emission_ring_axis() const { + return emission_ring_axis; +} + +real_t ParticlesMaterial::get_emission_ring_height() const { + return emission_ring_height; +} + +real_t ParticlesMaterial::get_emission_ring_radius() const { + return emission_ring_radius; +} + +real_t ParticlesMaterial::get_emission_ring_inner_radius() const { + return emission_ring_inner_radius; +} + void ParticlesMaterial::set_gravity(const Vector3 &p_gravity) { gravity = p_gravity; Vector3 gset = gravity; @@ -1025,12 +1120,12 @@ Vector3 ParticlesMaterial::get_gravity() const { return gravity; } -void ParticlesMaterial::set_lifetime_randomness(float p_lifetime) { +void ParticlesMaterial::set_lifetime_randomness(double p_lifetime) { lifetime_randomness = p_lifetime; RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->lifetime_randomness, lifetime_randomness); } -float ParticlesMaterial::get_lifetime_randomness() const { +double ParticlesMaterial::get_lifetime_randomness() const { return lifetime_randomness; } @@ -1040,10 +1135,6 @@ RID ParticlesMaterial::get_shader_rid() const { } void ParticlesMaterial::_validate_property(PropertyInfo &property) const { - if (property.name == "color" && color_ramp.is_valid()) { - property.usage = PROPERTY_USAGE_NONE; - } - if (property.name == "emission_sphere_radius" && emission_shape != EMISSION_SHAPE_SPHERE) { property.usage = PROPERTY_USAGE_NONE; } @@ -1052,7 +1143,7 @@ void ParticlesMaterial::_validate_property(PropertyInfo &property) const { property.usage = PROPERTY_USAGE_NONE; } - if ((property.name == "emission_point_texture" || property.name == "emission_color_texture") && (emission_shape < EMISSION_SHAPE_POINTS)) { + if ((property.name == "emission_point_texture" || property.name == "emission_color_texture") && (emission_shape != EMISSION_SHAPE_POINTS && emission_shape != EMISSION_SHAPE_DIRECTED_POINTS)) { property.usage = PROPERTY_USAGE_NONE; } @@ -1064,6 +1155,10 @@ void ParticlesMaterial::_validate_property(PropertyInfo &property) const { property.usage = PROPERTY_USAGE_NONE; } + if (property.name.begins_with("emission_ring_") && emission_shape != EMISSION_SHAPE_RING) { + property.usage = PROPERTY_USAGE_NONE; + } + if (property.name == "sub_emitter_frequency" && sub_emitter_mode != SUB_EMITTER_CONSTANT) { property.usage = PROPERTY_USAGE_NONE; } @@ -1087,11 +1182,12 @@ ParticlesMaterial::SubEmitterMode ParticlesMaterial::get_sub_emitter_mode() cons return sub_emitter_mode; } -void ParticlesMaterial::set_sub_emitter_frequency(float p_frequency) { +void ParticlesMaterial::set_sub_emitter_frequency(double p_frequency) { sub_emitter_frequency = p_frequency; RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->sub_emitter_frequency, 1.0 / p_frequency); //pass delta instead of frequency, since its easier to compute } -float ParticlesMaterial::get_sub_emitter_frequency() const { + +double ParticlesMaterial::get_sub_emitter_frequency() const { return sub_emitter_frequency; } @@ -1171,11 +1267,11 @@ void ParticlesMaterial::_bind_methods() { ClassDB::bind_method(D_METHOD("set_flatness", "amount"), &ParticlesMaterial::set_flatness); ClassDB::bind_method(D_METHOD("get_flatness"), &ParticlesMaterial::get_flatness); - ClassDB::bind_method(D_METHOD("set_param", "param", "value"), &ParticlesMaterial::set_param); - ClassDB::bind_method(D_METHOD("get_param", "param"), &ParticlesMaterial::get_param); + ClassDB::bind_method(D_METHOD("set_param_min", "param", "value"), &ParticlesMaterial::set_param_min); + ClassDB::bind_method(D_METHOD("get_param_min", "param"), &ParticlesMaterial::get_param_min); - ClassDB::bind_method(D_METHOD("set_param_randomness", "param", "randomness"), &ParticlesMaterial::set_param_randomness); - ClassDB::bind_method(D_METHOD("get_param_randomness", "param"), &ParticlesMaterial::get_param_randomness); + ClassDB::bind_method(D_METHOD("set_param_max", "param", "value"), &ParticlesMaterial::set_param_max); + ClassDB::bind_method(D_METHOD("get_param_max", "param"), &ParticlesMaterial::get_param_max); ClassDB::bind_method(D_METHOD("set_param_texture", "param", "texture"), &ParticlesMaterial::set_param_texture); ClassDB::bind_method(D_METHOD("get_param_texture", "param"), &ParticlesMaterial::get_param_texture); @@ -1210,6 +1306,18 @@ void ParticlesMaterial::_bind_methods() { ClassDB::bind_method(D_METHOD("set_emission_point_count", "point_count"), &ParticlesMaterial::set_emission_point_count); ClassDB::bind_method(D_METHOD("get_emission_point_count"), &ParticlesMaterial::get_emission_point_count); + ClassDB::bind_method(D_METHOD("set_emission_ring_axis", "axis"), &ParticlesMaterial::set_emission_ring_axis); + ClassDB::bind_method(D_METHOD("get_emission_ring_axis"), &ParticlesMaterial::get_emission_ring_axis); + + ClassDB::bind_method(D_METHOD("set_emission_ring_height", "height"), &ParticlesMaterial::set_emission_ring_height); + ClassDB::bind_method(D_METHOD("get_emission_ring_height"), &ParticlesMaterial::get_emission_ring_height); + + ClassDB::bind_method(D_METHOD("set_emission_ring_radius", "radius"), &ParticlesMaterial::set_emission_ring_radius); + ClassDB::bind_method(D_METHOD("get_emission_ring_radius"), &ParticlesMaterial::get_emission_ring_radius); + + ClassDB::bind_method(D_METHOD("set_emission_ring_inner_radius", "inner_radius"), &ParticlesMaterial::set_emission_ring_inner_radius); + ClassDB::bind_method(D_METHOD("get_emission_ring_inner_radius"), &ParticlesMaterial::get_emission_ring_inner_radius); + ClassDB::bind_method(D_METHOD("get_gravity"), &ParticlesMaterial::get_gravity); ClassDB::bind_method(D_METHOD("set_gravity", "accel_vec"), &ParticlesMaterial::set_gravity); @@ -1247,13 +1355,17 @@ void ParticlesMaterial::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime_randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_lifetime_randomness", "get_lifetime_randomness"); ADD_GROUP("Emission Shape", "emission_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_shape", PROPERTY_HINT_ENUM, "Point,Sphere,Box,Points,Directed Points"), "set_emission_shape", "get_emission_shape"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_shape", PROPERTY_HINT_ENUM, "Point,Sphere,Box,Points,Directed Points,Ring"), "set_emission_shape", "get_emission_shape"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_sphere_radius", PROPERTY_HINT_RANGE, "0.01,128,0.01,or_greater"), "set_emission_sphere_radius", "get_emission_sphere_radius"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_box_extents"), "set_emission_box_extents", "get_emission_box_extents"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "emission_point_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_emission_point_texture", "get_emission_point_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "emission_normal_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_emission_normal_texture", "get_emission_normal_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "emission_color_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_emission_color_texture", "get_emission_color_texture"); ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_point_count", PROPERTY_HINT_RANGE, "0,1000000,1"), "set_emission_point_count", "get_emission_point_count"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_ring_axis"), "set_emission_ring_axis", "get_emission_ring_axis"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height"), "set_emission_ring_height", "get_emission_ring_height"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius"), "set_emission_ring_radius", "get_emission_ring_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius"); ADD_GROUP("ParticleFlags", "particle_flag_"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_align_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_rotate_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ROTATE_Y); @@ -1265,54 +1377,54 @@ void ParticlesMaterial::_bind_methods() { ADD_GROUP("Gravity", ""); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "gravity"), "set_gravity", "get_gravity"); ADD_GROUP("Initial Velocity", "initial_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity", PROPERTY_HINT_RANGE, "0,1000,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_INITIAL_LINEAR_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_INITIAL_LINEAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_min", PROPERTY_HINT_RANGE, "0,1000,0.01,or_lesser,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_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_INITIAL_LINEAR_VELOCITY); ADD_GROUP("Angular Velocity", "angular_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ANGULAR_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGULAR_VELOCITY); + 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); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angular_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANGULAR_VELOCITY); ADD_GROUP("Orbit Velocity", "orbit_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ORBIT_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ORBIT_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_min", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_ORBIT_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_max", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_ORBIT_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "orbit_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ORBIT_VELOCITY); ADD_GROUP("Linear Accel", "linear_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_LINEAR_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_LINEAR_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_LINEAR_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_LINEAR_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "linear_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_LINEAR_ACCEL); ADD_GROUP("Radial Accel", "radial_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_RADIAL_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_RADIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_RADIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_RADIAL_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "radial_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_RADIAL_ACCEL); ADD_GROUP("Tangential Accel", "tangential_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_TANGENTIAL_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_TANGENTIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_TANGENTIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_TANGENTIAL_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "tangential_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_TANGENTIAL_ACCEL); ADD_GROUP("Damping", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping", PROPERTY_HINT_RANGE, "0,100,0.01,or_greater"), "set_param", "get_param", PARAM_DAMPING); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_min", PROPERTY_HINT_RANGE, "0,100,0.01,or_greater"), "set_param_min", "get_param_min", PARAM_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_max", PROPERTY_HINT_RANGE, "0,100,0.01,or_greater"), "set_param_max", "get_param_max", PARAM_DAMPING); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_DAMPING); ADD_GROUP("Angle", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param", "get_param", PARAM_ANGLE); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGLE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_min", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param_min", "get_param_min", PARAM_ANGLE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_max", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater,degrees"), "set_param_max", "get_param_max", PARAM_ANGLE); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angle_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANGLE); ADD_GROUP("Scale", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param", "get_param", PARAM_SCALE); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_SCALE); - ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "scale_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_SCALE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_min", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_min", "get_param_min", PARAM_SCALE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_max", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_max", "get_param_max", PARAM_SCALE); + ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "scale_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture,CurveXYZTexture"), "set_param_texture", "get_param_texture", PARAM_SCALE); ADD_GROUP("Color", ""); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "color_ramp", PROPERTY_HINT_RESOURCE_TYPE, "GradientTexture"), "set_color_ramp", "get_color_ramp"); ADD_GROUP("Hue Variation", "hue_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param", "get_param", PARAM_HUE_VARIATION); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_HUE_VARIATION); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_min", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param_min", "get_param_min", PARAM_HUE_VARIATION); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_max", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param_max", "get_param_max", PARAM_HUE_VARIATION); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "hue_variation_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_HUE_VARIATION); ADD_GROUP("Animation", "anim_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater"), "set_param", "get_param", PARAM_ANIM_SPEED); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANIM_SPEED); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_min", PROPERTY_HINT_RANGE, "0,16,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_ANIM_SPEED); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_max", PROPERTY_HINT_RANGE, "0,16,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_ANIM_SPEED); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_speed_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANIM_SPEED); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_ANIM_OFFSET); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANIM_OFFSET); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_min", PROPERTY_HINT_RANGE, "0,16,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_ANIM_OFFSET); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_max", PROPERTY_HINT_RANGE, "0,16,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_ANIM_OFFSET); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_offset_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANIM_OFFSET); ADD_GROUP("Sub Emitter", "sub_emitter_"); @@ -1353,6 +1465,7 @@ void ParticlesMaterial::_bind_methods() { BIND_ENUM_CONSTANT(EMISSION_SHAPE_BOX); BIND_ENUM_CONSTANT(EMISSION_SHAPE_POINTS); BIND_ENUM_CONSTANT(EMISSION_SHAPE_DIRECTED_POINTS); + BIND_ENUM_CONSTANT(EMISSION_SHAPE_RING); BIND_ENUM_CONSTANT(EMISSION_SHAPE_MAX); BIND_ENUM_CONSTANT(SUB_EMITTER_DISABLED); @@ -1367,21 +1480,37 @@ ParticlesMaterial::ParticlesMaterial() : set_direction(Vector3(1, 0, 0)); set_spread(45); set_flatness(0); - set_param(PARAM_INITIAL_LINEAR_VELOCITY, 0); - set_param(PARAM_ANGULAR_VELOCITY, 0); - set_param(PARAM_ORBIT_VELOCITY, 0); - set_param(PARAM_LINEAR_ACCEL, 0); - set_param(PARAM_RADIAL_ACCEL, 0); - set_param(PARAM_TANGENTIAL_ACCEL, 0); - set_param(PARAM_DAMPING, 0); - set_param(PARAM_ANGLE, 0); - set_param(PARAM_SCALE, 1); - set_param(PARAM_HUE_VARIATION, 0); - set_param(PARAM_ANIM_SPEED, 0); - set_param(PARAM_ANIM_OFFSET, 0); + set_param_min(PARAM_INITIAL_LINEAR_VELOCITY, 0); + set_param_min(PARAM_ANGULAR_VELOCITY, 0); + set_param_min(PARAM_ORBIT_VELOCITY, 0); + set_param_min(PARAM_LINEAR_ACCEL, 0); + set_param_min(PARAM_RADIAL_ACCEL, 0); + set_param_min(PARAM_TANGENTIAL_ACCEL, 0); + set_param_min(PARAM_DAMPING, 0); + set_param_min(PARAM_ANGLE, 0); + set_param_min(PARAM_SCALE, 1); + set_param_min(PARAM_HUE_VARIATION, 0); + set_param_min(PARAM_ANIM_SPEED, 0); + set_param_min(PARAM_ANIM_OFFSET, 0); + set_param_max(PARAM_INITIAL_LINEAR_VELOCITY, 0); + set_param_max(PARAM_ANGULAR_VELOCITY, 0); + set_param_max(PARAM_ORBIT_VELOCITY, 0); + set_param_max(PARAM_LINEAR_ACCEL, 0); + set_param_max(PARAM_RADIAL_ACCEL, 0); + set_param_max(PARAM_TANGENTIAL_ACCEL, 0); + set_param_max(PARAM_DAMPING, 0); + set_param_max(PARAM_ANGLE, 0); + set_param_max(PARAM_SCALE, 1); + set_param_max(PARAM_HUE_VARIATION, 0); + set_param_max(PARAM_ANIM_SPEED, 0); + set_param_max(PARAM_ANIM_OFFSET, 0); set_emission_shape(EMISSION_SHAPE_POINT); set_emission_sphere_radius(1); set_emission_box_extents(Vector3(1, 1, 1)); + set_emission_ring_axis(Vector3(0, 0, 1.0)); + set_emission_ring_height(1); + set_emission_ring_radius(1); + set_emission_ring_inner_radius(0); set_gravity(Vector3(0, -9.8, 0)); set_lifetime_randomness(0); @@ -1396,10 +1525,6 @@ ParticlesMaterial::ParticlesMaterial() : set_collision_friction(0.0); set_collision_use_scale(false); - for (int i = 0; i < PARAM_MAX; i++) { - set_param_randomness(Parameter(i), 0); - } - for (int i = 0; i < PARTICLE_FLAG_MAX; i++) { particle_flags[i] = false; } diff --git a/scene/resources/particles_material.h b/scene/resources/particles_material.h index 3f874bd68c..8ab26aff77 100644 --- a/scene/resources/particles_material.h +++ b/scene/resources/particles_material.h @@ -61,6 +61,7 @@ public: PARAM_MAX }; + // When extending, make sure not to overflow the size of the MaterialKey below. enum ParticleFlags { PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY, PARTICLE_FLAG_ROTATE_Y, @@ -68,15 +69,18 @@ public: PARTICLE_FLAG_MAX }; + // When extending, make sure not to overflow the size of the MaterialKey below. enum EmissionShape { EMISSION_SHAPE_POINT, EMISSION_SHAPE_SPHERE, EMISSION_SHAPE_BOX, EMISSION_SHAPE_POINTS, EMISSION_SHAPE_DIRECTED_POINTS, + EMISSION_SHAPE_RING, EMISSION_SHAPE_MAX }; + // When extending, make sure not to overflow the size of the MaterialKey below. enum SubEmitterMode { SUB_EMITTER_DISABLED, SUB_EMITTER_CONSTANT, @@ -87,11 +91,13 @@ public: private: union MaterialKey { + // The bit size of the struct must be kept below or equal to 32 bits. + // Consider this when extending ParticleFlags, EmissionShape, or SubEmitterMode. struct { uint32_t texture_mask : 16; uint32_t texture_color : 1; uint32_t particle_flags : 4; - uint32_t emission_shape : 2; + uint32_t emission_shape : 3; uint32_t invalid_key : 1; uint32_t has_emission_color : 1; uint32_t sub_emitter : 2; @@ -148,31 +154,31 @@ private: StringName direction; StringName spread; StringName flatness; - StringName initial_linear_velocity; - StringName initial_angle; - StringName angular_velocity; - StringName orbit_velocity; - StringName linear_accel; - StringName radial_accel; - StringName tangent_accel; - StringName damping; - StringName scale; - StringName hue_variation; - StringName anim_speed; - StringName anim_offset; - - StringName initial_linear_velocity_random; - StringName initial_angle_random; - StringName angular_velocity_random; - StringName orbit_velocity_random; - StringName linear_accel_random; - StringName radial_accel_random; - StringName tangent_accel_random; - StringName damping_random; - StringName scale_random; - StringName hue_variation_random; - StringName anim_speed_random; - StringName anim_offset_random; + StringName initial_linear_velocity_min; + StringName initial_angle_min; + StringName angular_velocity_min; + StringName orbit_velocity_min; + StringName linear_accel_min; + StringName radial_accel_min; + StringName tangent_accel_min; + StringName damping_min; + StringName scale_min; + StringName hue_variation_min; + StringName anim_speed_min; + StringName anim_offset_min; + + StringName initial_linear_velocity_max; + StringName initial_angle_max; + StringName angular_velocity_max; + StringName orbit_velocity_max; + StringName linear_accel_max; + StringName radial_accel_max; + StringName tangent_accel_max; + StringName damping_max; + StringName scale_max; + StringName hue_variation_max; + StringName anim_speed_max; + StringName anim_offset_max; StringName angle_texture; StringName angular_velocity_texture; @@ -195,6 +201,10 @@ private: StringName emission_texture_points; StringName emission_texture_normal; StringName emission_texture_color; + StringName emission_ring_axis; + StringName emission_ring_height; + StringName emission_ring_radius; + StringName emission_ring_inner_radius; StringName gravity; @@ -220,8 +230,8 @@ private: float spread; float flatness; - float parameters[PARAM_MAX]; - float randomness[PARAM_MAX]; + float params_min[PARAM_MAX]; + float params_max[PARAM_MAX]; Ref<Texture2D> tex_parameters[PARAM_MAX]; Color color; @@ -235,16 +245,20 @@ private: 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; int emission_point_count = 1; bool anim_loop; Vector3 gravity; - float lifetime_randomness; + double lifetime_randomness; SubEmitterMode sub_emitter_mode; - float sub_emitter_frequency; + double sub_emitter_frequency; int sub_emitter_amount_at_end; bool sub_emitter_keep_velocity; //do not save emission points here @@ -269,11 +283,11 @@ public: void set_flatness(float p_flatness); float get_flatness() const; - void set_param(Parameter p_param, float p_value); - float get_param(Parameter p_param) const; + void set_param_min(Parameter p_param, float p_value); + float get_param_min(Parameter p_param) const; - void set_param_randomness(Parameter p_param, float p_value); - float get_param_randomness(Parameter p_param) const; + void set_param_max(Parameter p_param, float p_value); + float get_param_max(Parameter p_param) const; void set_param_texture(Parameter p_param, const Ref<Texture2D> &p_texture); Ref<Texture2D> get_param_texture(Parameter p_param) const; @@ -288,26 +302,34 @@ public: bool get_particle_flag(ParticleFlags p_particle_flag) const; void set_emission_shape(EmissionShape p_shape); - void set_emission_sphere_radius(float p_radius); + void set_emission_sphere_radius(real_t p_radius); void set_emission_box_extents(Vector3 p_extents); void set_emission_point_texture(const Ref<Texture2D> &p_points); void set_emission_normal_texture(const Ref<Texture2D> &p_normals); void set_emission_color_texture(const Ref<Texture2D> &p_colors); + void set_emission_ring_axis(Vector3 p_axis); + void set_emission_ring_height(real_t p_height); + void set_emission_ring_radius(real_t p_radius); + void set_emission_ring_inner_radius(real_t p_radius); void set_emission_point_count(int p_count); EmissionShape get_emission_shape() const; - float get_emission_sphere_radius() const; + real_t get_emission_sphere_radius() const; Vector3 get_emission_box_extents() const; Ref<Texture2D> get_emission_point_texture() const; Ref<Texture2D> get_emission_normal_texture() const; Ref<Texture2D> get_emission_color_texture() const; + Vector3 get_emission_ring_axis() const; + real_t get_emission_ring_height() const; + real_t get_emission_ring_radius() const; + real_t get_emission_ring_inner_radius() const; int get_emission_point_count() const; void set_gravity(const Vector3 &p_gravity); Vector3 get_gravity() const; - void set_lifetime_randomness(float p_lifetime); - float get_lifetime_randomness() const; + void set_lifetime_randomness(double p_lifetime); + double get_lifetime_randomness() const; void set_attractor_interaction_enabled(bool p_enable); bool is_attractor_interaction_enabled() const; @@ -331,8 +353,8 @@ public: void set_sub_emitter_mode(SubEmitterMode p_sub_emitter_mode); SubEmitterMode get_sub_emitter_mode() const; - void set_sub_emitter_frequency(float p_frequency); - float get_sub_emitter_frequency() const; + void set_sub_emitter_frequency(double p_frequency); + double get_sub_emitter_frequency() const; void set_sub_emitter_amount_at_end(int p_amount); int get_sub_emitter_amount_at_end() const; diff --git a/scene/resources/polygon_path_finder.cpp b/scene/resources/polygon_path_finder.cpp index a08684a506..4dd3c874cb 100644 --- a/scene/resources/polygon_path_finder.cpp +++ b/scene/resources/polygon_path_finder.cpp @@ -417,9 +417,9 @@ void PolygonPathFinder::_set_data(const Dictionary &p_data) { } if (p_data.has("penalties")) { - Vector<float> penalties = p_data["penalties"]; + Vector<real_t> penalties = p_data["penalties"]; if (penalties.size() == pc) { - const float *pr2 = penalties.ptr(); + const real_t *pr2 = penalties.ptr(); for (int i = 0; i < pc; i++) { points.write[i].penalty = pr2[i]; } @@ -445,11 +445,11 @@ Dictionary PolygonPathFinder::_get_data() const { p.resize(MAX(0, points.size() - 2)); connections.resize(MAX(0, points.size() - 2)); ind.resize(edges.size() * 2); - Vector<float> penalties; + Vector<real_t> penalties; penalties.resize(MAX(0, points.size() - 2)); { Vector2 *wp = p.ptrw(); - float *pw = penalties.ptrw(); + real_t *pw = penalties.ptrw(); for (int i = 0; i < points.size() - 2; i++) { wp[i] = points[i].pos; diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp index 2b2ebb5c16..e7da41db9d 100644 --- a/scene/resources/primitive_meshes.cpp +++ b/scene/resources/primitive_meshes.cpp @@ -207,7 +207,7 @@ void PrimitiveMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_flip_faces", "flip_faces"), &PrimitiveMesh::set_flip_faces); ClassDB::bind_method(D_METHOD("get_flip_faces"), &PrimitiveMesh::get_flip_faces); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,StandardMaterial3D"), "set_material", "get_material"); + 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::BOOL, "flip_faces"), "set_flip_faces", "get_flip_faces"); } @@ -300,7 +300,7 @@ void CapsuleMesh::_create_mesh_array(Array &p_arr) const { z = cos(u * Math_TAU); Vector3 p = Vector3(x * radius * w, y, -z * radius * w); - points.push_back(p + Vector3(0.0, 0.5 * mid_height, 0.0)); + 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) uvs.push_back(Vector2(u, v * onethird)); @@ -328,8 +328,8 @@ void CapsuleMesh::_create_mesh_array(Array &p_arr) const { v = j; v /= (rings + 1); - y = mid_height * v; - y = (mid_height * 0.5) - y; + y = (height - 2.0 * radius) * v; + y = (0.5 * height - radius) - y; for (i = 0; i <= radial_segments; i++) { u = i; @@ -379,7 +379,7 @@ void CapsuleMesh::_create_mesh_array(Array &p_arr) const { z = cos(u2 * Math_TAU); Vector3 p = Vector3(x * radius * w, y, -z * radius * w); - points.push_back(p + Vector3(0.0, -0.5 * mid_height, 0.0)); + 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) uvs.push_back(Vector2(u2, twothirds + ((v - 1.0) * onethird))); @@ -410,8 +410,8 @@ void CapsuleMesh::_create_mesh_array(Array &p_arr) const { void CapsuleMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CapsuleMesh::set_radius); ClassDB::bind_method(D_METHOD("get_radius"), &CapsuleMesh::get_radius); - ClassDB::bind_method(D_METHOD("set_mid_height", "mid_height"), &CapsuleMesh::set_mid_height); - ClassDB::bind_method(D_METHOD("get_mid_height"), &CapsuleMesh::get_mid_height); + ClassDB::bind_method(D_METHOD("set_height", "height"), &CapsuleMesh::set_height); + ClassDB::bind_method(D_METHOD("get_height"), &CapsuleMesh::get_height); ClassDB::bind_method(D_METHOD("set_radial_segments", "segments"), &CapsuleMesh::set_radial_segments); ClassDB::bind_method(D_METHOD("get_radial_segments"), &CapsuleMesh::get_radial_segments); @@ -419,13 +419,16 @@ void CapsuleMesh::_bind_methods() { 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, "mid_height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_mid_height", "get_mid_height"); + 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::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"); } void CapsuleMesh::set_radius(const float p_radius) { radius = p_radius; + if (radius > height * 0.5) { + radius = height * 0.5; + } _request_update(); } @@ -433,13 +436,16 @@ float CapsuleMesh::get_radius() const { return radius; } -void CapsuleMesh::set_mid_height(const float p_mid_height) { - mid_height = p_mid_height; +void CapsuleMesh::set_height(const float p_height) { + height = p_height; + if (radius > height * 0.5) { + height = radius * 2; + } _request_update(); } -float CapsuleMesh::get_mid_height() const { - return mid_height; +float CapsuleMesh::get_height() const { + return height; } void CapsuleMesh::set_radial_segments(const int p_segments) { @@ -714,7 +720,7 @@ int BoxMesh::get_subdivide_depth() const { BoxMesh::BoxMesh() {} /** - CylinderMesh + CylinderMesh */ void CylinderMesh::_create_mesh_array(Array &p_arr) const { @@ -955,7 +961,7 @@ void PlaneMesh::_create_mesh_array(Array &p_arr) const { u /= (subdivide_w + 1.0); v /= (subdivide_d + 1.0); - points.push_back(Vector3(-x, 0.0, -z)); + points.push_back(Vector3(-x, 0.0, -z) + center_offset); normals.push_back(Vector3(0.0, 1.0, 0.0)); ADD_TANGENT(1.0, 0.0, 0.0, 1.0); uvs.push_back(Vector2(1.0 - u, 1.0 - v)); /* 1.0 - uv to match orientation with Quad */ @@ -993,10 +999,13 @@ void PlaneMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("get_subdivide_width"), &PlaneMesh::get_subdivide_width); ClassDB::bind_method(D_METHOD("set_subdivide_depth", "subdivide"), &PlaneMesh::set_subdivide_depth); ClassDB::bind_method(D_METHOD("get_subdivide_depth"), &PlaneMesh::get_subdivide_depth); + 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::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"); } void PlaneMesh::set_size(const Size2 &p_size) { @@ -1026,6 +1035,15 @@ int PlaneMesh::get_subdivide_depth() const { return subdivide_d; } +void PlaneMesh::set_center_offset(const Vector3 p_offset) { + center_offset = p_offset; + _request_update(); +} + +Vector3 PlaneMesh::get_center_offset() const { + return center_offset; +} + PlaneMesh::PlaneMesh() {} /** @@ -1326,10 +1344,10 @@ void QuadMesh::_create_mesh_array(Array &p_arr) const { Vector2 _size = Vector2(size.x / 2.0f, size.y / 2.0f); Vector3 quad_faces[4] = { - Vector3(-_size.x, -_size.y, 0), - Vector3(-_size.x, _size.y, 0), - Vector3(_size.x, _size.y, 0), - Vector3(_size.x, -_size.y, 0), + Vector3(-_size.x, -_size.y, 0) + center_offset, + Vector3(-_size.x, _size.y, 0) + center_offset, + Vector3(_size.x, _size.y, 0) + center_offset, + Vector3(_size.x, -_size.y, 0) + center_offset, }; static const int indices[6] = { @@ -1365,7 +1383,11 @@ void QuadMesh::_create_mesh_array(Array &p_arr) const { void QuadMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_size", "size"), &QuadMesh::set_size); ClassDB::bind_method(D_METHOD("get_size"), &QuadMesh::get_size); + 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"); } QuadMesh::QuadMesh() { @@ -1381,6 +1403,15 @@ Size2 QuadMesh::get_size() const { return size; } +void QuadMesh::set_center_offset(Vector3 p_center_offset) { + center_offset = p_center_offset; + _request_update(); +} + +Vector3 QuadMesh::get_center_offset() const { + return center_offset; +} + /** SphereMesh */ @@ -1389,6 +1420,8 @@ void SphereMesh::_create_mesh_array(Array &p_arr) const { int i, j, prevrow, thisrow, point; float x, y, z; + float scale = height * (is_hemisphere ? 1.0 : 0.5); + // set our bounding box Vector<Vector3> points; @@ -1412,7 +1445,7 @@ void SphereMesh::_create_mesh_array(Array &p_arr) const { v /= (rings + 1); w = sin(Math_PI * v); - y = height * (is_hemisphere ? 1.0 : 0.5) * cos(Math_PI * v); + y = scale * cos(Math_PI * v); for (i = 0; i <= radial_segments; i++) { float u = i; @@ -1427,7 +1460,8 @@ void SphereMesh::_create_mesh_array(Array &p_arr) const { } else { Vector3 p = Vector3(x * radius * w, y, z * radius * w); points.push_back(p); - normals.push_back(p.normalized()); + Vector3 normal = Vector3(x * radius * w * scale, y / scale, z * radius * w * scale); + normals.push_back(normal.normalized()); }; ADD_TANGENT(z, 0.0, -x, 1.0) uvs.push_back(Vector2(u, v)); diff --git a/scene/resources/primitive_meshes.h b/scene/resources/primitive_meshes.h index bd6f94921e..7915cb0028 100644 --- a/scene/resources/primitive_meshes.h +++ b/scene/resources/primitive_meshes.h @@ -108,7 +108,7 @@ class CapsuleMesh : public PrimitiveMesh { private: float radius = 1.0; - float mid_height = 1.0; + float height = 3.0; int radial_segments = 64; int rings = 8; @@ -120,8 +120,8 @@ public: void set_radius(const float p_radius); float get_radius() const; - void set_mid_height(const float p_mid_height); - float get_mid_height() const; + void set_height(const float p_height); + float get_height() const; void set_radial_segments(const int p_segments); int get_radial_segments() const; @@ -211,6 +211,7 @@ private: Size2 size = Size2(2.0, 2.0); int subdivide_w = 0; int subdivide_d = 0; + Vector3 center_offset; protected: static void _bind_methods(); @@ -226,6 +227,9 @@ public: void set_subdivide_depth(const int p_divisions); int get_subdivide_depth() const; + void set_center_offset(const Vector3 p_offset); + Vector3 get_center_offset() const; + PlaneMesh(); }; @@ -274,6 +278,7 @@ class QuadMesh : public PrimitiveMesh { private: Size2 size = Size2(1.0, 1.0); + Vector3 center_offset; protected: static void _bind_methods(); @@ -284,6 +289,9 @@ public: void set_size(const Size2 &p_size); Size2 get_size() const; + + void set_center_offset(const Vector3 p_offset); + Vector3 get_center_offset() const; }; /** diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index ee61e64ed3..341ce22185 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -35,8 +35,9 @@ #include "core/io/resource_format_binary.h" #include "core/version.h" -//version 2: changed names for basis, aabb, Vectors, etc. -#define FORMAT_VERSION 2 +// Version 2: changed names for Basis, AABB, Vectors, etc. +// Version 3: new string ID for ext/subresources, breaks forward compat. +#define FORMAT_VERSION 3 #include "core/io/dir_access.h" #include "core/version.h" @@ -56,22 +57,23 @@ Ref<Resource> ResourceLoaderText::get_resource() { Error ResourceLoaderText::_parse_sub_resource_dummy(DummyReadData *p_data, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) { VariantParser::Token token; VariantParser::get_token(p_stream, token, line, r_err_str); - if (token.type != VariantParser::TK_NUMBER) { - r_err_str = "Expected number (sub-resource index)"; + if (token.type != VariantParser::TK_NUMBER && token.type != VariantParser::TK_STRING) { + r_err_str = "Expected number (old style) or string (sub-resource index)"; return ERR_PARSE_ERROR; } - int index = token.value; + String unique_id = token.value; - if (!p_data->resource_map.has(index)) { + if (!p_data->resource_map.has(unique_id)) { Ref<DummyResource> dr; dr.instantiate(); - dr->set_subindex(index); - p_data->resource_map[index] = dr; - p_data->resource_set.insert(dr); + 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_res = p_data->resource_map[index]; + r_res = p_data->resource_map[unique_id]; VariantParser::get_token(p_stream, token, line, r_err_str); if (token.type != VariantParser::TK_PARENTHESIS_CLOSE) { @@ -85,12 +87,12 @@ Error ResourceLoaderText::_parse_sub_resource_dummy(DummyReadData *p_data, Varia Error ResourceLoaderText::_parse_ext_resource_dummy(DummyReadData *p_data, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) { VariantParser::Token token; VariantParser::get_token(p_stream, token, line, r_err_str); - if (token.type != VariantParser::TK_NUMBER) { - r_err_str = "Expected number (sub-resource index)"; + if (token.type != VariantParser::TK_NUMBER && token.type != VariantParser::TK_STRING) { + r_err_str = "Expected number (old style sub-resource index) or String (ext-resource ID)"; return ERR_PARSE_ERROR; } - int id = token.value; + String id = token.value; ERR_FAIL_COND_V(!p_data->rev_external_resources.has(id), ERR_PARSE_ERROR); @@ -108,14 +110,14 @@ Error ResourceLoaderText::_parse_ext_resource_dummy(DummyReadData *p_data, Varia Error ResourceLoaderText::_parse_sub_resource(VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) { VariantParser::Token token; VariantParser::get_token(p_stream, token, line, r_err_str); - if (token.type != VariantParser::TK_NUMBER) { - r_err_str = "Expected number (sub-resource index)"; + if (token.type != VariantParser::TK_NUMBER && token.type != VariantParser::TK_STRING) { + r_err_str = "Expected number (old style sub-resource index) or string"; return ERR_PARSE_ERROR; } - int index = token.value; - ERR_FAIL_COND_V(!int_resources.has(index), ERR_INVALID_PARAMETER); - r_res = int_resources[index]; + String id = token.value; + ERR_FAIL_COND_V(!int_resources.has(id), ERR_INVALID_PARAMETER); + r_res = int_resources[id]; VariantParser::get_token(p_stream, token, line, r_err_str); if (token.type != VariantParser::TK_PARENTHESIS_CLOSE) { @@ -129,16 +131,16 @@ Error ResourceLoaderText::_parse_sub_resource(VariantParser::Stream *p_stream, R Error ResourceLoaderText::_parse_ext_resource(VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) { VariantParser::Token token; VariantParser::get_token(p_stream, token, line, r_err_str); - if (token.type != VariantParser::TK_NUMBER) { - r_err_str = "Expected number (sub-resource index)"; + if (token.type != VariantParser::TK_NUMBER && token.type != VariantParser::TK_STRING) { + r_err_str = "Expected number (old style sub-resource index) or String (ext-resource ID)"; return ERR_PARSE_ERROR; } - int id = token.value; + String id = token.value; if (!ignore_resource_parsing) { if (!ext_resources.has(id)) { - r_err_str = "Can't load cached ext-resource #" + itos(id); + r_err_str = "Can't load cached ext-resource id: " + id; return ERR_PARSE_ERROR; } @@ -409,9 +411,20 @@ Error ResourceLoaderText::load() { String path = next_tag.fields["path"]; String type = next_tag.fields["type"]; - int index = next_tag.fields["id"]; + String id = next_tag.fields["id"]; + + if (next_tag.fields.has("uid")) { + String uidt = next_tag.fields["uid"]; + ResourceUID::ID uid = ResourceUID::get_singleton()->text_to_id(uidt); + if (uid != ResourceUID::INVALID_ID && ResourceUID::get_singleton()->has_id(uid)) { + // If a UID is found and the path is valid, it will be used, otherwise, it falls back to the path. + path = ResourceUID::get_singleton()->get_id_path(uid); + } else { + WARN_PRINT(String(res_path + ":" + itos(lines) + " - ext_resource, invalid UUID: " + uidt + " - using text path instead: " + path).utf8().get_data()); + } + } - if (path.find("://") == -1 && path.is_rel_path()) { + if (path.find("://") == -1 && path.is_relative_path()) { // path is relative to file being loaded, so convert to a resource path path = ProjectSettings::get_singleton()->localize_path(local_path.get_base_dir().plus_file(path)); } @@ -453,14 +466,14 @@ Error ResourceLoaderText::load() { } else { #ifdef TOOLS_ENABLED //remember ID for saving - res->set_id_for_path(local_path, index); + res->set_id_for_path(local_path, id); #endif } er.cache = res; } - ext_resources[index] = er; + ext_resources[id] = er; error = VariantParser::parse_tag(&stream, lines, error_text, next_tag, &rp); @@ -489,15 +502,15 @@ Error ResourceLoaderText::load() { if (!next_tag.fields.has("id")) { error = ERR_FILE_CORRUPT; - error_text = "Missing 'index' in external resource tag"; + error_text = "Missing 'id' in external resource tag"; _printerr(); return error; } String type = next_tag.fields["type"]; - int id = next_tag.fields["id"]; + String id = next_tag.fields["id"]; - String path = local_path + "::" + itos(id); + String path = local_path + "::" + id; //bool exists=ResourceCache::has(path); @@ -545,6 +558,12 @@ Error ResourceLoaderText::load() { resource_current++; + int_resources[id] = res; //always assign int resources + if (do_assign && cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { + res->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE); + res->set_scene_unique_id(id); + } + while (true) { String assign; Variant value; @@ -572,12 +591,6 @@ Error ResourceLoaderText::load() { } } - int_resources[id] = res; //always assign int resources - if (do_assign && cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { - res->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE); - res->set_subindex(id); - } - if (progress && resources_total > 0) { *progress = resource_current / float(resources_total); } @@ -736,7 +749,7 @@ void ResourceLoaderText::get_dependencies(FileAccess *p_f, List<String> *p_depen if (!next_tag.fields.has("id")) { error = ERR_FILE_CORRUPT; - error_text = "Missing 'index' in external resource tag"; + error_text = "Missing 'id' in external resource tag"; _printerr(); return; } @@ -744,7 +757,18 @@ void ResourceLoaderText::get_dependencies(FileAccess *p_f, List<String> *p_depen String path = next_tag.fields["path"]; String type = next_tag.fields["type"]; - if (path.find("://") == -1 && path.is_rel_path()) { + bool using_uid = false; + if (next_tag.fields.has("uid")) { + //if uid exists, return uid in text format, not the path + String uidt = next_tag.fields["uid"]; + ResourceUID::ID uid = ResourceUID::get_singleton()->text_to_id(uidt); + if (uid != ResourceUID::INVALID_ID) { + path = ResourceUID::get_singleton()->id_to_text(uid); + using_uid = true; + } + } + + if (!using_uid && path.find("://") == -1 && path.is_relative_path()) { // path is relative to file being loaded, so convert to a resource path path = ProjectSettings::get_singleton()->localize_path(local_path.get_base_dir().plus_file(path)); } @@ -814,9 +838,17 @@ Error ResourceLoaderText::rename_dependencies(FileAccess *p_f, const String &p_p } String path = next_tag.fields["path"]; - int index = next_tag.fields["id"]; + String id = next_tag.fields["id"]; String type = next_tag.fields["type"]; + if (next_tag.fields.has("uid")) { + String uidt = next_tag.fields["uid"]; + ResourceUID::ID uid = ResourceUID::get_singleton()->text_to_id(uidt); + if (uid != ResourceUID::INVALID_ID && ResourceUID::get_singleton()->has_id(uid)) { + // If a UID is found and the path is valid, it will be used, otherwise, it falls back to the path. + path = ResourceUID::get_singleton()->get_id_path(uid); + } + } bool relative = false; if (!path.begins_with("res://")) { path = base_path.plus_file(path).simplify_path(); @@ -833,7 +865,14 @@ Error ResourceLoaderText::rename_dependencies(FileAccess *p_f, const String &p_p path = base_path.path_to_file(path); } - fw->store_line("[ext_resource path=\"" + path + "\" type=\"" + type + "\" id=" + itos(index) + "]"); + String s = "[ext_resource type=\"" + type + "\""; + + ResourceUID::ID uid = ResourceSaver::get_resource_id_for_path(path); + if (uid != ResourceUID::INVALID_ID) { + s += " uid=\"" + ResourceUID::get_singleton()->id_to_text(uid) + "\""; + } + s += " path=\"" + path + "\" id=\"" + id + "\"]"; + fw->store_line(s); // Bundled. tag_end = f->get_position(); } @@ -919,6 +958,12 @@ void ResourceLoaderText::open(FileAccess *p_f, bool p_skip_first_tag) { return; } + if (tag.fields.has("uid")) { + res_uid = ResourceUID::get_singleton()->text_to_id(tag.fields["uid"]); + } else { + res_uid = ResourceUID::INVALID_ID; + } + if (tag.fields.has("load_steps")) { resources_total = tag.fields["load_steps"]; } else { @@ -937,7 +982,6 @@ void ResourceLoaderText::open(FileAccess *p_f, bool p_skip_first_tag) { rp.ext_func = _parse_ext_resources; rp.sub_func = _parse_sub_resources; - rp.func = nullptr; rp.userdata = this; } @@ -974,7 +1018,12 @@ Error ResourceLoaderText::save_as_binary(FileAccess *p_f, const String &p_path) bs_save_unicode_string(wf.f, is_scene ? "PackedScene" : resource_type); wf->store_64(0); //offset to import metadata, this is no longer used - for (int i = 0; i < 14; i++) { + + wf->store_32(ResourceFormatSaverBinaryInstance::FORMAT_FLAG_NAMED_SCENE_IDS | ResourceFormatSaverBinaryInstance::FORMAT_FLAG_UIDS); + + wf->store_64(res_uid); + + for (int i = 0; i < ResourceFormatSaverBinaryInstance::RESERVED_FIELDS; i++) { wf->store_32(0); // reserved } @@ -1015,17 +1064,23 @@ Error ResourceLoaderText::save_as_binary(FileAccess *p_f, const String &p_path) String path = next_tag.fields["path"]; String type = next_tag.fields["type"]; - int index = next_tag.fields["id"]; + String id = next_tag.fields["id"]; + ResourceUID::ID uid = ResourceUID::INVALID_ID; + if (next_tag.fields.has("uid")) { + String uidt = next_tag.fields["uid"]; + uid = ResourceUID::get_singleton()->text_to_id(uidt); + } bs_save_unicode_string(wf.f, type); bs_save_unicode_string(wf.f, path); + wf->store_64(uid); int lindex = dummy_read.external_resources.size(); Ref<DummyResource> dr; dr.instantiate(); dr->set_path("res://dummy" + itos(lindex)); //anything is good to detect it for saving as external dummy_read.external_resources[dr] = lindex; - dummy_read.rev_external_resources[index] = dr; + dummy_read.rev_external_resources[id] = dr; error = VariantParser::parse_tag(&stream, lines, error_text, next_tag, &rp); @@ -1069,7 +1124,7 @@ Error ResourceLoaderText::save_as_binary(FileAccess *p_f, const String &p_path) if (!next_tag.fields.has("id")) { error = ERR_FILE_CORRUPT; - error_text = "Missing 'index' in external resource tag"; + error_text = "Missing 'id' in external resource tag"; _printerr(); return error; } @@ -1114,7 +1169,7 @@ Error ResourceLoaderText::save_as_binary(FileAccess *p_f, const String &p_path) if (assign != String()) { Map<StringName, int> empty_string_map; //unused bs_save_unicode_string(wf2, assign, true); - ResourceFormatSaverBinaryInstance::write_variant(wf2, value, dummy_read.resource_set, dummy_read.external_resources, empty_string_map); + ResourceFormatSaverBinaryInstance::write_variant(wf2, value, dummy_read.resource_index_map, dummy_read.external_resources, empty_string_map); prop_count++; } else if (next_tag.name != String()) { @@ -1165,17 +1220,17 @@ Error ResourceLoaderText::save_as_binary(FileAccess *p_f, const String &p_path) int prop_count = 0; - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) { + for (const PropertyInfo &E : props) { + if (!(E.usage & PROPERTY_USAGE_STORAGE)) { continue; } - String name = E->get().name; + String name = E.name; Variant value = packed_scene->get(name); Map<StringName, int> empty_string_map; //unused bs_save_unicode_string(wf2, name, true); - ResourceFormatSaverBinaryInstance::write_variant(wf2, value, dummy_read.resource_set, dummy_read.external_resources, empty_string_map); + ResourceFormatSaverBinaryInstance::write_variant(wf2, value, dummy_read.resource_index_map, dummy_read.external_resources, empty_string_map); prop_count++; } @@ -1255,6 +1310,32 @@ String ResourceLoaderText::recognize(FileAccess *p_f) { return tag.fields["type"]; } +ResourceUID::ID ResourceLoaderText::get_uid(FileAccess *p_f) { + error = OK; + + lines = 1; + f = p_f; + + stream.f = f; + + ignore_resource_parsing = true; + + VariantParser::Tag tag; + Error err = VariantParser::parse_tag(&stream, lines, error_text, tag); + + if (err) { + _printerr(); + return ResourceUID::INVALID_ID; + } + + if (tag.fields.has("uid")) { //field is optional + String uidt = tag.fields["uid"]; + return ResourceUID::get_singleton()->text_to_id(uidt); + } + + return ResourceUID::INVALID_ID; +} + ///////////////////// 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) { @@ -1275,7 +1356,6 @@ RES ResourceFormatLoaderText::load(const String &p_path, const String &p_origina loader.local_path = ProjectSettings::get_singleton()->localize_path(path); loader.progress = r_progress; loader.res_path = loader.local_path; - //loader.set_local_path( ProjectSettings::get_singleton()->localize_path(p_path) ); loader.open(f); err = loader.load(); if (r_error) { @@ -1318,7 +1398,7 @@ String ResourceFormatLoaderText::get_resource_type(const String &p_path) const { return String(); } - //for anyhting else must test.. + // ...for anything else must test... FileAccess *f = FileAccess::open(p_path, FileAccess::READ); if (!f) { @@ -1328,11 +1408,28 @@ String ResourceFormatLoaderText::get_resource_type(const String &p_path) const { ResourceLoaderText loader; loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path); loader.res_path = loader.local_path; - //loader.set_local_path( ProjectSettings::get_singleton()->localize_path(p_path) ); String r = loader.recognize(f); return ClassDB::get_compatibility_remapped_class(r); } +ResourceUID::ID ResourceFormatLoaderText::get_resource_uid(const String &p_path) const { + String ext = p_path.get_extension().to_lower(); + + if (ext != "tscn" && ext != "tres") { + return ResourceUID::INVALID_ID; + } + + FileAccess *f = FileAccess::open(p_path, FileAccess::READ); + if (!f) { + return ResourceUID::INVALID_ID; //could not read + } + + ResourceLoaderText loader; + loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path); + loader.res_path = loader.local_path; + return loader.get_uid(f); +} + void ResourceFormatLoaderText::get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types) { FileAccess *f = FileAccess::open(p_path, FileAccess::READ); if (!f) { @@ -1342,7 +1439,6 @@ void ResourceFormatLoaderText::get_dependencies(const String &p_path, List<Strin ResourceLoaderText loader; loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path); loader.res_path = loader.local_path; - //loader.set_local_path( ProjectSettings::get_singleton()->localize_path(p_path) ); loader.get_dependencies(f, p_dependencies, p_add_types); } @@ -1355,7 +1451,6 @@ Error ResourceFormatLoaderText::rename_dependencies(const String &p_path, const ResourceLoaderText loader; loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path); loader.res_path = loader.local_path; - //loader.set_local_path( ProjectSettings::get_singleton()->localize_path(p_path) ); return loader.rename_dependencies(f, p_path, p_map); } @@ -1371,7 +1466,6 @@ Error ResourceFormatLoaderText::convert_file_to_binary(const String &p_src_path, const String &path = p_src_path; loader.local_path = ProjectSettings::get_singleton()->localize_path(path); loader.res_path = loader.local_path; - //loader.set_local_path( ProjectSettings::get_singleton()->localize_path(p_path) ); loader.open(f); return loader.save_as_binary(f, p_dst_path); } @@ -1394,10 +1488,10 @@ String ResourceFormatSaverTextInstance::_write_resources(void *ud, const RES &p_ String ResourceFormatSaverTextInstance::_write_resource(const RES &res) { if (external_resources.has(res)) { - return "ExtResource( " + itos(external_resources[res]) + " )"; + return "ExtResource( \"" + external_resources[res] + "\" )"; } else { if (internal_resources.has(res)) { - return "SubResource( " + itos(internal_resources[res]) + " )"; + return "SubResource( \"" + internal_resources[res] + "\" )"; } else if (res->get_path().length() && res->get_path().find("::") == -1) { if (res->get_path() == local_path) { //circular reference attempt return "null"; @@ -1426,8 +1520,11 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant, ERR_PRINT("Circular reference to resource being saved found: '" + local_path + "' will be null next time it's loaded."); return; } - int index = external_resources.size(); - external_resources[res] = index; + + // Use a numeric ID as a base, because they are sorted in natural order before saving. + // This increases the chances of thread loading to fetch them first. + String id = itos(external_resources.size() + 1) + "_" + Resource::generate_scene_unique_id(); + external_resources[res] = id; return; } @@ -1483,8 +1580,8 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant, Dictionary d = p_variant; List<Variant> keys; d.get_key_list(&keys); - for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { - Variant v = d[E->get()]; + for (const Variant &E : keys) { + Variant v = d[E]; _find_resources(v); } } break; @@ -1513,11 +1610,11 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r takeover_paths = false; } - // save resources + // Save resources. _find_resources(p_resource, true); if (packed_scene.is_valid()) { - //add instances to external resources if saving a packed scene + // Add instances to external resources if saving a packed scene. for (int i = 0; i < packed_scene->get_state()->get_node_count(); i++) { if (packed_scene->get_state()->is_node_instance_placeholder(i)) { continue; @@ -1525,8 +1622,8 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r Ref<PackedScene> instance = packed_scene->get_state()->get_node_instance(i); if (instance.is_valid() && !external_resources.has(instance)) { - int index = external_resources.size(); - external_resources[instance] = index; + int index = external_resources.size() + 1; + external_resources[instance] = itos(index) + "_" + Resource::generate_scene_unique_id(); // Keep the order for improved thread loading performance. } } } @@ -1537,64 +1634,74 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r title += "type=\"" + p_resource->get_class() + "\" "; } int load_steps = saved_resources.size() + external_resources.size(); - /* - if (packed_scene.is_valid()) { - load_steps+=packed_scene->get_node_count(); - } - //no, better to not use load steps from nodes, no point to that - */ if (load_steps > 1) { title += "load_steps=" + itos(load_steps) + " "; } title += "format=" + itos(FORMAT_VERSION) + ""; + ResourceUID::ID uid = ResourceSaver::get_resource_id_for_path(local_path, true); + + if (uid != ResourceUID::INVALID_ID) { + title += " uid=\"" + ResourceUID::get_singleton()->id_to_text(uid) + "\""; + } + f->store_string(title); - f->store_line("]\n"); //one empty line + f->store_line("]\n"); // One empty line. } #ifdef TOOLS_ENABLED - //keep order from cached ids - Set<int> cached_ids_found; - for (Map<RES, int>::Element *E = external_resources.front(); E; E = E->next()) { - int cached_id = E->key()->get_id_for_path(local_path); - if (cached_id < 0 || cached_ids_found.has(cached_id)) { - E->get() = -1; //reset + // Keep order from cached ids. + Set<String> cached_ids_found; + for (Map<RES, String>::Element *E = external_resources.front(); E; E = E->next()) { + String cached_id = E->key()->get_id_for_path(local_path); + if (cached_id == "" || cached_ids_found.has(cached_id)) { + int sep_pos = E->get().find("_"); + if (sep_pos != -1) { + E->get() = E->get().substr(0, sep_pos + 1); // Keep the order found, for improved thread loading performance. + } else { + E->get() = ""; + } + } else { E->get() = cached_id; cached_ids_found.insert(cached_id); } } - //create IDs for non cached resources - for (Map<RES, int>::Element *E = external_resources.front(); E; E = E->next()) { - if (cached_ids_found.has(E->get())) { //already cached, go on + // Create IDs for non cached resources. + for (Map<RES, String>::Element *E = external_resources.front(); E; E = E->next()) { + if (cached_ids_found.has(E->get())) { // Already cached, go on. continue; } - int attempt = 1; //start from one, more readable format - while (cached_ids_found.has(attempt)) { - attempt++; + String attempt; + while (true) { + attempt = E->get() + Resource::generate_scene_unique_id(); + if (!cached_ids_found.has(attempt)) { + break; + } } cached_ids_found.insert(attempt); E->get() = attempt; - //update also in resource + // Update also in resource. Ref<Resource> res = E->key(); res->set_id_for_path(local_path, attempt); } #else - //make sure to start from one, as it makes format more readable - for (Map<RES, int>::Element *E = external_resources.front(); E; E = E->next()) { - E->get() = E->get() + 1; + // Make sure to start from one, as it makes format more readable. + int counter = 1; + for (Map<RES, String>::Element *E = external_resources.front(); E; E = E->next()) { + E->get() = itos(counter++); } #endif Vector<ResourceSort> sorted_er; - for (Map<RES, int>::Element *E = external_resources.front(); E; E = E->next()) { + for (Map<RES, String>::Element *E = external_resources.front(); E; E = E->next()) { ResourceSort rs; rs.resource = E->key(); - rs.index = E->get(); + rs.id = E->get(); sorted_er.push_back(rs); } @@ -1603,23 +1710,30 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r for (int i = 0; i < sorted_er.size(); i++) { String p = sorted_er[i].resource->get_path(); - f->store_string("[ext_resource path=\"" + p + "\" type=\"" + sorted_er[i].resource->get_save_class() + "\" id=" + itos(sorted_er[i].index) + "]\n"); //bundled + String s = "[ext_resource type=\"" + sorted_er[i].resource->get_save_class() + "\""; + + ResourceUID::ID uid = ResourceSaver::get_resource_id_for_path(p, false); + if (uid != ResourceUID::INVALID_ID) { + s += " uid=\"" + ResourceUID::get_singleton()->id_to_text(uid) + "\""; + } + s += " path=\"" + p + "\" id=\"" + sorted_er[i].id + "\"]\n"; + f->store_string(s); // Bundled. } if (external_resources.size()) { - f->store_line(String()); //separate + f->store_line(String()); // Separate. } - Set<int> used_indices; + Set<String> used_unique_ids; for (List<RES>::Element *E = saved_resources.front(); E; E = E->next()) { RES res = E->get(); if (E->next() && (res->get_path() == "" || res->get_path().find("::") != -1)) { - if (res->get_subindex() != 0) { - if (used_indices.has(res->get_subindex())) { - res->set_subindex(0); //repeated + if (res->get_scene_unique_id() != "") { + if (used_unique_ids.has(res->get_scene_unique_id())) { + res->set_scene_unique_id(""); // Repeated. } else { - used_indices.insert(res->get_subindex()); + used_unique_ids.insert(res->get_scene_unique_id()); } } } @@ -1631,31 +1745,35 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r bool main = (E->next() == nullptr); if (main && packed_scene.is_valid()) { - break; //save as a scene + break; // Save as a scene. } if (main) { f->store_line("[resource]"); } else { String line = "[sub_resource "; - if (res->get_subindex() == 0) { - int new_subindex = 1; - if (used_indices.size()) { - new_subindex = used_indices.back()->get() + 1; + if (res->get_scene_unique_id() == "") { + String new_id; + while (true) { + new_id = res->get_class() + "_" + Resource::generate_scene_unique_id(); + + if (!used_unique_ids.has(new_id)) { + break; + } } - res->set_subindex(new_subindex); - used_indices.insert(new_subindex); + res->set_scene_unique_id(new_id); + used_unique_ids.insert(new_id); } - int idx = res->get_subindex(); - line += "type=\"" + res->get_class() + "\" id=" + itos(idx); - f->store_line(line + "]"); + String id = res->get_scene_unique_id(); + line += "type=\"" + res->get_class() + "\" id=\"" + id; + f->store_line(line + "\"]"); if (takeover_paths) { - res->set_path(p_path + "::" + itos(idx), true); + res->set_path(p_path + "::" + id, true); } - internal_resources[res] = idx; + internal_resources[res] = id; #ifdef TOOLS_ENABLED res->set_edited(false); #endif @@ -1663,7 +1781,6 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r List<PropertyInfo> property_list; res->get_property_list(&property_list); - //property_list.sort(); for (List<PropertyInfo>::Element *PE = property_list.front(); PE; PE = PE->next()) { if (skip_editor && PE->get().name.begins_with("__editor")) { continue; @@ -1704,7 +1821,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r } if (packed_scene.is_valid()) { - //if this is a scene, save nodes and connections! + // If this is a scene, save nodes and connections! Ref<SceneState> state = packed_scene->get_state(); for (int i = 0; i < state->get_node_count(); i++) { StringName type = state->get_node_type(i); @@ -1732,10 +1849,16 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r } 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, + // since it's rare to have more than 5 groups assigned to a single node. groups.sort_custom<StringName::AlphCompare>(); - String sgroups = " groups=[\n"; + String sgroups = " groups=["; for (int j = 0; j < groups.size(); j++) { - sgroups += "\"" + String(groups[j]).c_escape() + "\",\n"; + sgroups += "\"" + String(groups[j]).c_escape() + "\""; + if (j < groups.size() - 1) { + sgroups += ", "; + } } sgroups += "]"; header += sgroups; @@ -1812,7 +1935,6 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r } f->close(); - //memdelete(f); return OK; } diff --git a/scene/resources/resource_format_text.h b/scene/resources/resource_format_text.h index f5d9cca859..373e71b2c4 100644 --- a/scene/resources/resource_format_text.h +++ b/scene/resources/resource_format_text.h @@ -58,10 +58,8 @@ class ResourceLoaderText { bool ignore_resource_parsing = false; - //Map<String,String> remaps; - - Map<int, ExtResource> ext_resources; - Map<int, RES> int_resources; + Map<String, ExtResource> ext_resources; + Map<String, RES> int_resources; int resources_total = 0; int resource_current = 0; @@ -76,8 +74,9 @@ class ResourceLoaderText { mutable int lines = 0; + ResourceUID::ID res_uid = ResourceUID::INVALID_ID; + Map<String, String> remaps; - //void _printerr(); 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); } @@ -92,9 +91,9 @@ class ResourceLoaderText { struct DummyReadData { Map<RES, int> external_resources; - Map<int, RES> rev_external_resources; - Set<RES> resource_set; - Map<int, RES> resource_map; + Map<String, RES> rev_external_resources; + Map<RES, int> resource_index_map; + Map<String, RES> 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((DummyReadData *)(p_self), p_stream, r_res, line, r_err_str); } @@ -123,6 +122,7 @@ public: void open(FileAccess *p_f, bool p_skip_first_tag = false); String recognize(FileAccess *p_f); + ResourceUID::ID get_uid(FileAccess *p_f); void get_dependencies(FileAccess *p_f, List<String> *p_dependencies, bool p_add_types); Error rename_dependencies(FileAccess *p_f, const String &p_path, const Map<String, String> &p_map); @@ -139,6 +139,7 @@ public: 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); @@ -168,14 +169,14 @@ class ResourceFormatSaverTextInstance { Set<RES> resource_set; List<RES> saved_resources; - Map<RES, int> external_resources; - Map<RES, int> internal_resources; + Map<RES, String> external_resources; + Map<RES, String> internal_resources; struct ResourceSort { RES resource; - int index = 0; + String id; bool operator<(const ResourceSort &p_right) const { - return index < p_right.index; + return id.naturalnocasecmp_to(p_right.id) < 0; } }; diff --git a/scene/resources/ray_shape_2d.cpp b/scene/resources/separation_ray_shape_2d.cpp index fb8f4b9985..0acd6d268d 100644 --- a/scene/resources/ray_shape_2d.cpp +++ b/scene/resources/separation_ray_shape_2d.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* ray_shape_2d.cpp */ +/* separation_ray_shape_2d.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,20 +28,20 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "ray_shape_2d.h" +#include "separation_ray_shape_2d.h" #include "servers/physics_server_2d.h" #include "servers/rendering_server.h" -void RayShape2D::_update_shape() { +void SeparationRayShape2D::_update_shape() { Dictionary d; d["length"] = length; - d["slips_on_slope"] = slips_on_slope; + d["slide_on_slope"] = slide_on_slope; PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), d); emit_changed(); } -void RayShape2D::draw(const RID &p_to_rid, const Color &p_color) { +void SeparationRayShape2D::draw(const RID &p_to_rid, const Color &p_color) { const Vector2 target_position = Vector2(0, get_length()); const float max_arrow_size = 6; @@ -72,7 +72,7 @@ void RayShape2D::draw(const RID &p_to_rid, const Color &p_color) { RS::get_singleton()->canvas_item_add_primitive(p_to_rid, pts, cols, Vector<Point2>(), RID()); } -Rect2 RayShape2D::get_rect() const { +Rect2 SeparationRayShape2D::get_rect() const { Rect2 rect; rect.position = Vector2(); rect.expand_to(Vector2(0, length)); @@ -80,40 +80,40 @@ Rect2 RayShape2D::get_rect() const { return rect; } -real_t RayShape2D::get_enclosing_radius() const { +real_t SeparationRayShape2D::get_enclosing_radius() const { return length; } -void RayShape2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_length", "length"), &RayShape2D::set_length); - ClassDB::bind_method(D_METHOD("get_length"), &RayShape2D::get_length); +void SeparationRayShape2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_length", "length"), &SeparationRayShape2D::set_length); + ClassDB::bind_method(D_METHOD("get_length"), &SeparationRayShape2D::get_length); - ClassDB::bind_method(D_METHOD("set_slips_on_slope", "active"), &RayShape2D::set_slips_on_slope); - ClassDB::bind_method(D_METHOD("get_slips_on_slope"), &RayShape2D::get_slips_on_slope); + 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"), "set_length", "get_length"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slips_on_slope"), "set_slips_on_slope", "get_slips_on_slope"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_slope"), "set_slide_on_slope", "get_slide_on_slope"); } -void RayShape2D::set_length(real_t p_length) { +void SeparationRayShape2D::set_length(real_t p_length) { length = p_length; _update_shape(); } -real_t RayShape2D::get_length() const { +real_t SeparationRayShape2D::get_length() const { return length; } -void RayShape2D::set_slips_on_slope(bool p_active) { - slips_on_slope = p_active; +void SeparationRayShape2D::set_slide_on_slope(bool p_active) { + slide_on_slope = p_active; _update_shape(); } -bool RayShape2D::get_slips_on_slope() const { - return slips_on_slope; +bool SeparationRayShape2D::get_slide_on_slope() const { + return slide_on_slope; } -RayShape2D::RayShape2D() : - Shape2D(PhysicsServer2D::get_singleton()->ray_shape_create()) { +SeparationRayShape2D::SeparationRayShape2D() : + Shape2D(PhysicsServer2D::get_singleton()->separation_ray_shape_create()) { _update_shape(); } diff --git a/scene/resources/ray_shape_2d.h b/scene/resources/separation_ray_shape_2d.h index 56ecfa2722..5b74e6c727 100644 --- a/scene/resources/ray_shape_2d.h +++ b/scene/resources/separation_ray_shape_2d.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* ray_shape_2d.h */ +/* separation_ray_shape_2d.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,16 +28,16 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef RAY_SHAPE_2D_H -#define RAY_SHAPE_2D_H +#ifndef SEPARATION_RAY_SHAPE_2D_H +#define SEPARATION_RAY_SHAPE_2D_H #include "scene/resources/shape_2d.h" -class RayShape2D : public Shape2D { - GDCLASS(RayShape2D, Shape2D); +class SeparationRayShape2D : public Shape2D { + GDCLASS(SeparationRayShape2D, Shape2D); real_t length = 20.0; - bool slips_on_slope = false; + bool slide_on_slope = false; void _update_shape(); @@ -48,14 +48,14 @@ public: void set_length(real_t p_length); real_t get_length() const; - void set_slips_on_slope(bool p_active); - bool get_slips_on_slope() const; + void set_slide_on_slope(bool p_active); + bool get_slide_on_slope() const; virtual void draw(const RID &p_to_rid, const Color &p_color) override; virtual Rect2 get_rect() const override; virtual real_t get_enclosing_radius() const override; - RayShape2D(); + SeparationRayShape2D(); }; -#endif // RAY_SHAPE_2D_H +#endif // SEPARATION_RAY_SHAPE_2D_H diff --git a/scene/resources/ray_shape_3d.cpp b/scene/resources/separation_ray_shape_3d.cpp index 5446b4daab..376e04c844 100644 --- a/scene/resources/ray_shape_3d.cpp +++ b/scene/resources/separation_ray_shape_3d.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* ray_shape_3d.cpp */ +/* separation_ray_shape_3d.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,11 +28,11 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "ray_shape_3d.h" +#include "separation_ray_shape_3d.h" #include "servers/physics_server_3d.h" -Vector<Vector3> RayShape3D::get_debug_mesh_lines() const { +Vector<Vector3> SeparationRayShape3D::get_debug_mesh_lines() const { Vector<Vector3> points; points.push_back(Vector3()); points.push_back(Vector3(0, 0, get_length())); @@ -40,51 +40,51 @@ Vector<Vector3> RayShape3D::get_debug_mesh_lines() const { return points; } -real_t RayShape3D::get_enclosing_radius() const { +real_t SeparationRayShape3D::get_enclosing_radius() const { return length; } -void RayShape3D::_update_shape() { +void SeparationRayShape3D::_update_shape() { Dictionary d; d["length"] = length; - d["slips_on_slope"] = slips_on_slope; + d["slide_on_slope"] = slide_on_slope; PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), d); Shape3D::_update_shape(); } -void RayShape3D::set_length(float p_length) { +void SeparationRayShape3D::set_length(float p_length) { length = p_length; _update_shape(); notify_change_to_owners(); } -float RayShape3D::get_length() const { +float SeparationRayShape3D::get_length() const { return length; } -void RayShape3D::set_slips_on_slope(bool p_active) { - slips_on_slope = p_active; +void SeparationRayShape3D::set_slide_on_slope(bool p_active) { + slide_on_slope = p_active; _update_shape(); notify_change_to_owners(); } -bool RayShape3D::get_slips_on_slope() const { - return slips_on_slope; +bool SeparationRayShape3D::get_slide_on_slope() const { + return slide_on_slope; } -void RayShape3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_length", "length"), &RayShape3D::set_length); - ClassDB::bind_method(D_METHOD("get_length"), &RayShape3D::get_length); +void SeparationRayShape3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_length", "length"), &SeparationRayShape3D::set_length); + ClassDB::bind_method(D_METHOD("get_length"), &SeparationRayShape3D::get_length); - ClassDB::bind_method(D_METHOD("set_slips_on_slope", "active"), &RayShape3D::set_slips_on_slope); - ClassDB::bind_method(D_METHOD("get_slips_on_slope"), &RayShape3D::get_slips_on_slope); + 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,4096,0.001"), "set_length", "get_length"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slips_on_slope"), "set_slips_on_slope", "get_slips_on_slope"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_slope"), "set_slide_on_slope", "get_slide_on_slope"); } -RayShape3D::RayShape3D() : - Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_RAY)) { +SeparationRayShape3D::SeparationRayShape3D() : + Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_SEPARATION_RAY)) { /* Code copied from setters to prevent the use of uninitialized variables */ _update_shape(); notify_change_to_owners(); diff --git a/scene/resources/ray_shape_3d.h b/scene/resources/separation_ray_shape_3d.h index 2da6311321..54058b6095 100644 --- a/scene/resources/ray_shape_3d.h +++ b/scene/resources/separation_ray_shape_3d.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* ray_shape_3d.h */ +/* separation_ray_shape_3d.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,14 +28,14 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef RAY_SHAPE_H -#define RAY_SHAPE_H +#ifndef SEPARATION_RAY_SHAPE_H +#define SEPARATION_RAY_SHAPE_H #include "scene/resources/shape_3d.h" -class RayShape3D : public Shape3D { - GDCLASS(RayShape3D, Shape3D); +class SeparationRayShape3D : public Shape3D { + GDCLASS(SeparationRayShape3D, Shape3D); float length = 1.0; - bool slips_on_slope = false; + bool slide_on_slope = false; protected: static void _bind_methods(); @@ -45,12 +45,12 @@ public: void set_length(float p_length); float get_length() const; - void set_slips_on_slope(bool p_active); - bool get_slips_on_slope() const; + void set_slide_on_slope(bool p_active); + bool get_slide_on_slope() const; virtual Vector<Vector3> get_debug_mesh_lines() const override; virtual real_t get_enclosing_radius() const override; - RayShape3D(); + SeparationRayShape3D(); }; -#endif // RAY_SHAPE_H +#endif // SEPARATION_RAY_SHAPE_H diff --git a/scene/resources/shader.cpp b/scene/resources/shader.cpp index f19d08dbb1..44d524f142 100644 --- a/scene/resources/shader.cpp +++ b/scene/resources/shader.cpp @@ -72,13 +72,13 @@ void Shader::get_param_list(List<PropertyInfo> *p_params) const { params_cache.clear(); params_cache_dirty = false; - for (List<PropertyInfo>::Element *E = local.front(); E; E = E->next()) { - PropertyInfo pi = E->get(); + for (PropertyInfo &pi : local) { if (default_textures.has(pi.name)) { //do not show default textures continue; } + String original_name = pi.name; pi.name = "shader_param/" + pi.name; - params_cache[pi.name] = E->get().name; + params_cache[pi.name] = original_name; if (p_params) { //small little hack if (pi.type == Variant::RID) { diff --git a/scene/resources/skeleton_modification_2d.cpp b/scene/resources/skeleton_modification_2d.cpp index b52a60006a..e533fb054a 100644 --- a/scene/resources/skeleton_modification_2d.cpp +++ b/scene/resources/skeleton_modification_2d.cpp @@ -44,7 +44,7 @@ /////////////////////////////////////// void SkeletonModification2D::_execute(float p_delta) { - call("_execute", p_delta); + GDVIRTUAL_CALL(_execute, p_delta); if (!enabled) { return; @@ -59,11 +59,11 @@ void SkeletonModification2D::_setup_modification(SkeletonModificationStack2D *p_ WARN_PRINT("Could not setup modification with name " + get_name()); } - call("_setup_modification", p_stack); + GDVIRTUAL_CALL(_setup_modification, Ref<SkeletonModificationStack2D>(p_stack)); } void SkeletonModification2D::_draw_editor_gizmo() { - call("_draw_editor_gizmo"); + GDVIRTUAL_CALL(_draw_editor_gizmo); } void SkeletonModification2D::set_enabled(bool p_enabled) { @@ -166,13 +166,13 @@ void SkeletonModification2D::editor_draw_angle_constraints(Bone2D *p_operation_b if (operation_bone_parent_bone) { stack->skeleton->draw_set_transform( - stack->skeleton->get_global_transform().affine_inverse().xform(p_operation_bone->get_global_position()), + stack->skeleton->to_local(p_operation_bone->get_global_position()), operation_bone_parent_bone->get_global_rotation() - stack->skeleton->get_global_rotation()); } else { - stack->skeleton->draw_set_transform(stack->skeleton->get_global_transform().affine_inverse().xform(p_operation_bone->get_global_position())); + stack->skeleton->draw_set_transform(stack->skeleton->to_local(p_operation_bone->get_global_position())); } } else { - stack->skeleton->draw_set_transform(stack->skeleton->get_global_transform().affine_inverse().xform(p_operation_bone->get_global_position())); + stack->skeleton->draw_set_transform(stack->skeleton->to_local(p_operation_bone->get_global_position())); } if (p_constraint_inverted) { @@ -186,7 +186,7 @@ void SkeletonModification2D::editor_draw_angle_constraints(Bone2D *p_operation_b stack->skeleton->draw_line(Vector2(0, 0), Vector2(Math::cos(arc_angle_max), Math::sin(arc_angle_max)) * p_operation_bone->get_length(), bone_ik_color, 1.0); } else { - stack->skeleton->draw_set_transform(stack->skeleton->get_global_transform().affine_inverse().xform(p_operation_bone->get_global_position())); + stack->skeleton->draw_set_transform(stack->skeleton->to_local(p_operation_bone->get_global_position())); stack->skeleton->draw_arc(Vector2(0, 0), p_operation_bone->get_length(), 0, Math_PI * 2, 32, bone_ik_color, 1.0); stack->skeleton->draw_line(Vector2(0, 0), Vector2(1, 0) * p_operation_bone->get_length(), bone_ik_color, 1.0); } @@ -228,9 +228,9 @@ bool SkeletonModification2D::get_editor_draw_gizmo() const { } void SkeletonModification2D::_bind_methods() { - BIND_VMETHOD(MethodInfo("_execute", PropertyInfo(Variant::FLOAT, "delta"))); - BIND_VMETHOD(MethodInfo("_setup_modification", PropertyInfo(Variant::OBJECT, "modification_stack", PROPERTY_HINT_RESOURCE_TYPE, "SkeletonModificationStack2D"))); - BIND_VMETHOD(MethodInfo("_draw_editor_gizmo")); + GDVIRTUAL_BIND(_execute, "delta"); + GDVIRTUAL_BIND(_setup_modification, "modification_stack") + GDVIRTUAL_BIND(_draw_editor_gizmo) ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &SkeletonModification2D::set_enabled); ClassDB::bind_method(D_METHOD("get_enabled"), &SkeletonModification2D::get_enabled); diff --git a/scene/resources/skeleton_modification_2d.h b/scene/resources/skeleton_modification_2d.h index 18633e55cb..aaddb9136e 100644 --- a/scene/resources/skeleton_modification_2d.h +++ b/scene/resources/skeleton_modification_2d.h @@ -57,6 +57,10 @@ protected: bool _print_execution_error(bool p_condition, String p_message); + GDVIRTUAL1(_execute, double) + GDVIRTUAL1(_setup_modification, Ref<SkeletonModificationStack2D>) + GDVIRTUAL0(_draw_editor_gizmo) + public: virtual void _execute(float _delta); virtual void _setup_modification(SkeletonModificationStack2D *p_stack); diff --git a/scene/resources/skeleton_modification_2d_ccdik.cpp b/scene/resources/skeleton_modification_2d_ccdik.cpp index 7ea60e584e..6eab8d4afe 100644 --- a/scene/resources/skeleton_modification_2d_ccdik.cpp +++ b/scene/resources/skeleton_modification_2d_ccdik.cpp @@ -201,18 +201,18 @@ void SkeletonModification2DCCDIK::_execute_ccdik_joint(int p_joint_idx, Node2D * if (ccdik_data.rotate_from_joint) { // To rotate from the joint, simply look at the target! operation_transform.set_rotation( - operation_transform.looking_at(p_target->get_global_transform().get_origin()).get_rotation() - operation_bone->get_bone_angle()); + operation_transform.looking_at(p_target->get_global_position()).get_rotation() - operation_bone->get_bone_angle()); } else { // How to rotate from the tip: get the difference of rotation needed from the tip to the target, from the perspective of the joint. // Because we are only using the offset, we do not need to account for the bone angle of the Bone2D node. - float joint_to_tip = operation_transform.get_origin().angle_to_point(p_tip->get_global_transform().get_origin()); - float joint_to_target = operation_transform.get_origin().angle_to_point(p_target->get_global_transform().get_origin()); + float joint_to_tip = operation_transform.get_origin().angle_to_point(p_tip->get_global_position()); + float joint_to_target = operation_transform.get_origin().angle_to_point(p_target->get_global_position()); operation_transform.set_rotation( operation_transform.get_rotation() + (joint_to_target - joint_to_tip)); } // Reset scale - operation_transform.set_scale(operation_bone->get_global_transform().get_scale()); + operation_transform.set_scale(operation_bone->get_global_scale()); // Apply constraints in globalspace: if (ccdik_data.enable_constraint && !ccdik_data.constraint_in_localspace) { diff --git a/scene/resources/skeleton_modification_2d_fabrik.cpp b/scene/resources/skeleton_modification_2d_fabrik.cpp index aef852f7e4..6e9429034f 100644 --- a/scene/resources/skeleton_modification_2d_fabrik.cpp +++ b/scene/resources/skeleton_modification_2d_fabrik.cpp @@ -149,37 +149,28 @@ void SkeletonModification2DFABRIK::_execute(float p_delta) { return; } fabrik_transform_chain.write[i] = joint_bone2d_node->get_global_transform(); - - // Apply magnet positions - if (i == 0) { - continue; // The origin cannot use a magnet position! - } else { - Transform2D joint_trans = fabrik_transform_chain[i]; - joint_trans.set_origin(joint_trans.get_origin() + fabrik_data_chain[i].magnet_position); - fabrik_transform_chain.write[i] = joint_trans; - } } Bone2D *final_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[fabrik_data_chain.size() - 1].bone2d_node_cache)); - float final_bone2d_angle = final_bone2d_node->get_global_transform().get_rotation(); + float final_bone2d_angle = final_bone2d_node->get_global_rotation(); if (fabrik_data_chain[fabrik_data_chain.size() - 1].use_target_rotation) { final_bone2d_angle = target_global_pose.get_rotation(); } Vector2 final_bone2d_direction = Vector2(Math::cos(final_bone2d_angle), Math::sin(final_bone2d_angle)); float final_bone2d_length = final_bone2d_node->get_length() * MIN(final_bone2d_node->get_global_scale().x, final_bone2d_node->get_global_scale().y); - float target_distance = (final_bone2d_node->get_global_transform().get_origin() + (final_bone2d_direction * final_bone2d_length)).distance_to(target->get_global_transform().get_origin()); + float target_distance = (final_bone2d_node->get_global_position() + (final_bone2d_direction * final_bone2d_length)).distance_to(target->get_global_position()); chain_iterations = 0; while (target_distance > chain_tolarance) { chain_backwards(); chain_forwards(); - final_bone2d_angle = final_bone2d_node->get_global_transform().get_rotation(); + final_bone2d_angle = final_bone2d_node->get_global_rotation(); if (fabrik_data_chain[fabrik_data_chain.size() - 1].use_target_rotation) { final_bone2d_angle = target_global_pose.get_rotation(); } final_bone2d_direction = Vector2(Math::cos(final_bone2d_angle), Math::sin(final_bone2d_angle)); - target_distance = (final_bone2d_node->get_global_transform().get_origin() + (final_bone2d_direction * final_bone2d_length)).distance_to(target->get_global_transform().get_origin()); + target_distance = (final_bone2d_node->get_global_position() + (final_bone2d_direction * final_bone2d_length)).distance_to(target->get_global_position()); chain_iterations += 1; if (chain_iterations >= chain_max_iterations) { @@ -210,7 +201,7 @@ void SkeletonModification2DFABRIK::_execute(float p_delta) { chain_trans.set_rotation(chain_trans.get_rotation() - joint_bone2d_node->get_bone_angle()); // Reset scale - chain_trans.set_scale(joint_bone2d_node->get_global_transform().get_scale()); + chain_trans.set_scale(joint_bone2d_node->get_global_scale()); // Apply to the bone, and to the override joint_bone2d_node->set_global_transform(chain_trans); @@ -223,6 +214,11 @@ void SkeletonModification2DFABRIK::chain_backwards() { Bone2D *final_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[final_joint_index].bone2d_node_cache)); Transform2D final_bone2d_trans = fabrik_transform_chain[final_joint_index]; + // Apply magnet position + if (final_joint_index != 0) { + final_bone2d_trans.set_origin(final_bone2d_trans.get_origin() + fabrik_data_chain[final_joint_index].magnet_position); + } + // Set the rotation of the tip bone final_bone2d_trans = final_bone2d_trans.looking_at(target_global_pose.get_origin()); @@ -245,6 +241,11 @@ void SkeletonModification2DFABRIK::chain_backwards() { Bone2D *current_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[i].bone2d_node_cache)); Transform2D current_pose = fabrik_transform_chain[i]; + // Apply magnet position + if (i != 0) { + current_pose.set_origin(current_pose.get_origin() + fabrik_data_chain[i].magnet_position); + } + float current_bone2d_node_length = current_bone2d_node->get_length() * MIN(current_bone2d_node->get_global_scale().x, current_bone2d_node->get_global_scale().y); float length = current_bone2d_node_length / (previous_pose.get_origin() - current_pose.get_origin()).length(); Vector2 finish_position = previous_pose.get_origin().lerp(current_pose.get_origin(), length); diff --git a/scene/resources/skeleton_modification_2d_jiggle.cpp b/scene/resources/skeleton_modification_2d_jiggle.cpp index 2547083336..84abc9d020 100644 --- a/scene/resources/skeleton_modification_2d_jiggle.cpp +++ b/scene/resources/skeleton_modification_2d_jiggle.cpp @@ -171,7 +171,7 @@ void SkeletonModification2DJiggle::_execute_jiggle_joint(int p_joint_idx, Node2D } Transform2D operation_bone_trans = operation_bone->get_global_transform(); - Vector2 target_position = p_target->get_global_transform().get_origin(); + Vector2 target_position = p_target->get_global_position(); jiggle_data_chain.write[p_joint_idx].force = (target_position - jiggle_data_chain[p_joint_idx].dynamic_position) * jiggle_data_chain[p_joint_idx].stiffness * p_delta; @@ -215,7 +215,7 @@ void SkeletonModification2DJiggle::_execute_jiggle_joint(int p_joint_idx, Node2D operation_bone_trans.set_rotation(operation_bone_trans.get_rotation() - operation_bone->get_bone_angle()); // Reset scale - operation_bone_trans.set_scale(operation_bone->get_global_transform().get_scale()); + operation_bone_trans.set_scale(operation_bone->get_global_scale()); operation_bone->set_global_transform(operation_bone_trans); stack->skeleton->set_bone_local_pose_override(jiggle_data_chain[p_joint_idx].bone_idx, operation_bone->get_transform(), stack->strength, true); @@ -244,7 +244,7 @@ void SkeletonModification2DJiggle::_setup_modification(SkeletonModificationStack int bone_idx = jiggle_data_chain[i].bone_idx; if (bone_idx > 0 && bone_idx < stack->skeleton->get_bone_count()) { Bone2D *bone2d_node = stack->skeleton->get_bone(bone_idx); - jiggle_data_chain.write[i].dynamic_position = bone2d_node->get_global_transform().get_origin(); + jiggle_data_chain.write[i].dynamic_position = bone2d_node->get_global_position(); } } } diff --git a/scene/resources/skeleton_modification_2d_lookat.cpp b/scene/resources/skeleton_modification_2d_lookat.cpp index fd5c8c7cc2..2da770f012 100644 --- a/scene/resources/skeleton_modification_2d_lookat.cpp +++ b/scene/resources/skeleton_modification_2d_lookat.cpp @@ -147,7 +147,7 @@ void SkeletonModification2DLookAt::_execute(float p_delta) { // Look at the target! operation_transform = operation_transform.looking_at(target_trans.get_origin()); // Apply whatever scale it had prior to looking_at - operation_transform.set_scale(operation_bone->get_global_transform().get_scale()); + operation_transform.set_scale(operation_bone->get_global_scale()); // Account for the direction the bone faces in: operation_transform.set_rotation(operation_transform.get_rotation() - operation_bone->get_bone_angle()); diff --git a/scene/resources/skeleton_modification_2d_twoboneik.cpp b/scene/resources/skeleton_modification_2d_twoboneik.cpp index 0a91290015..88d80a501f 100644 --- a/scene/resources/skeleton_modification_2d_twoboneik.cpp +++ b/scene/resources/skeleton_modification_2d_twoboneik.cpp @@ -142,7 +142,7 @@ void SkeletonModification2DTwoBoneIK::_execute(float p_delta) { // http://theorangeduck.com/page/simple-two-joint // https://www.alanzucconi.com/2018/05/02/ik-2d-2/ // With modifications by TwistedTwigleg - Vector2 target_difference = target->get_global_transform().get_origin() - joint_one_bone->get_global_transform().get_origin(); + Vector2 target_difference = target->get_global_position() - joint_one_bone->get_global_position(); float joint_one_to_target = target_difference.length(); float angle_atan = Math::atan2(target_difference.y, target_difference.x); @@ -206,7 +206,7 @@ void SkeletonModification2DTwoBoneIK::_draw_editor_gizmo() { return; } stack->skeleton->draw_set_transform( - stack->skeleton->get_global_transform().affine_inverse().xform(operation_bone_one->get_global_position()), + stack->skeleton->to_local(operation_bone_one->get_global_position()), operation_bone_one->get_global_rotation() - stack->skeleton->get_global_rotation()); Color bone_ik_color = Color(1.0, 0.65, 0.0, 0.4); diff --git a/scene/resources/skeleton_modification_3d.cpp b/scene/resources/skeleton_modification_3d.cpp new file mode 100644 index 0000000000..ee02ede2d5 --- /dev/null +++ b/scene/resources/skeleton_modification_3d.cpp @@ -0,0 +1,162 @@ +/*************************************************************************/ +/* skeleton_modification_3d.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "skeleton_modification_3d.h" +#include "scene/3d/skeleton_3d.h" + +void SkeletonModification3D::_execute(real_t p_delta) { + GDVIRTUAL_CALL(_execute, p_delta); + + if (!enabled) + return; +} + +void SkeletonModification3D::_setup_modification(SkeletonModificationStack3D *p_stack) { + stack = p_stack; + if (stack) { + is_setup = true; + } else { + WARN_PRINT("Could not setup modification with name " + this->get_name()); + } + + GDVIRTUAL_CALL(_setup_modification, Ref<SkeletonModificationStack3D>(p_stack)); +} + +void SkeletonModification3D::set_enabled(bool p_enabled) { + enabled = p_enabled; +} + +bool SkeletonModification3D::get_enabled() { + return enabled; +} + +// Helper function. Needed for CCDIK. +real_t SkeletonModification3D::clamp_angle(real_t p_angle, real_t p_min_bound, real_t p_max_bound, bool p_invert) { + // Map to the 0 to 360 range (in radians though) instead of the -180 to 180 range. + if (p_angle < 0) { + p_angle = Math_TAU + p_angle; + } + + // Make min and max in the range of 0 to 360 (in radians), and make sure they are in the right order + if (p_min_bound < 0) { + p_min_bound = Math_TAU + p_min_bound; + } + if (p_max_bound < 0) { + p_max_bound = Math_TAU + p_max_bound; + } + if (p_min_bound > p_max_bound) { + real_t tmp = p_min_bound; + p_min_bound = p_max_bound; + p_max_bound = tmp; + } + + // Note: May not be the most optimal way to clamp, but it always constraints to the nearest angle. + if (p_invert == false) { + if (p_angle < p_min_bound || p_angle > p_max_bound) { + Vector2 min_bound_vec = Vector2(Math::cos(p_min_bound), Math::sin(p_min_bound)); + Vector2 max_bound_vec = Vector2(Math::cos(p_max_bound), Math::sin(p_max_bound)); + Vector2 angle_vec = Vector2(Math::cos(p_angle), Math::sin(p_angle)); + + if (angle_vec.distance_squared_to(min_bound_vec) <= angle_vec.distance_squared_to(max_bound_vec)) { + p_angle = p_min_bound; + } else { + p_angle = p_max_bound; + } + } + } else { + if (p_angle > p_min_bound && p_angle < p_max_bound) { + Vector2 min_bound_vec = Vector2(Math::cos(p_min_bound), Math::sin(p_min_bound)); + Vector2 max_bound_vec = Vector2(Math::cos(p_max_bound), Math::sin(p_max_bound)); + Vector2 angle_vec = Vector2(Math::cos(p_angle), Math::sin(p_angle)); + + if (angle_vec.distance_squared_to(min_bound_vec) <= angle_vec.distance_squared_to(max_bound_vec)) { + p_angle = p_min_bound; + } else { + p_angle = p_max_bound; + } + } + } + return p_angle; +} + +bool SkeletonModification3D::_print_execution_error(bool p_condition, String p_message) { + // If the modification is not setup, don't bother printing the error + if (!is_setup) { + return p_condition; + } + + if (p_condition && !execution_error_found) { + ERR_PRINT(p_message); + execution_error_found = true; + } + return p_condition; +} + +Ref<SkeletonModificationStack3D> SkeletonModification3D::get_modification_stack() { + return stack; +} + +void SkeletonModification3D::set_is_setup(bool p_is_setup) { + is_setup = p_is_setup; +} + +bool SkeletonModification3D::get_is_setup() const { + return is_setup; +} + +void SkeletonModification3D::set_execution_mode(int p_mode) { + execution_mode = p_mode; +} + +int SkeletonModification3D::get_execution_mode() const { + return execution_mode; +} + +void SkeletonModification3D::_bind_methods() { + GDVIRTUAL_BIND(_execute, "delta"); + GDVIRTUAL_BIND(_setup_modification, "modification_stack") + + ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &SkeletonModification3D::set_enabled); + ClassDB::bind_method(D_METHOD("get_enabled"), &SkeletonModification3D::get_enabled); + ClassDB::bind_method(D_METHOD("get_modification_stack"), &SkeletonModification3D::get_modification_stack); + ClassDB::bind_method(D_METHOD("set_is_setup", "is_setup"), &SkeletonModification3D::set_is_setup); + ClassDB::bind_method(D_METHOD("get_is_setup"), &SkeletonModification3D::get_is_setup); + ClassDB::bind_method(D_METHOD("set_execution_mode", "execution_mode"), &SkeletonModification3D::set_execution_mode); + ClassDB::bind_method(D_METHOD("get_execution_mode"), &SkeletonModification3D::get_execution_mode); + ClassDB::bind_method(D_METHOD("clamp_angle", "angle", "min", "max", "invert"), &SkeletonModification3D::clamp_angle); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "get_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "execution_mode", PROPERTY_HINT_ENUM, "process, physics_process"), "set_execution_mode", "get_execution_mode"); +} + +SkeletonModification3D::SkeletonModification3D() { + stack = nullptr; + is_setup = false; +} diff --git a/scene/resources/skeleton_modification_3d.h b/scene/resources/skeleton_modification_3d.h new file mode 100644 index 0000000000..fb1f3d33d1 --- /dev/null +++ b/scene/resources/skeleton_modification_3d.h @@ -0,0 +1,79 @@ +/*************************************************************************/ +/* skeleton_modification_3d.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SKELETONMODIFICATION3D_H +#define SKELETONMODIFICATION3D_H + +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/skeleton_modification_stack_3d.h" + +class SkeletonModificationStack3D; + +class SkeletonModification3D : public Resource { + GDCLASS(SkeletonModification3D, Resource); + friend class Skeleton3D; + friend class SkeletonModificationStack3D; + +protected: + static void _bind_methods(); + + SkeletonModificationStack3D *stack; + int execution_mode = 0; // 0 = process + + bool enabled = true; + bool is_setup = false; + bool execution_error_found = false; + + bool _print_execution_error(bool p_condition, String p_message); + + GDVIRTUAL1(_execute, double) + GDVIRTUAL1(_setup_modification, Ref<SkeletonModificationStack3D>) + +public: + virtual void _execute(real_t p_delta); + virtual void _setup_modification(SkeletonModificationStack3D *p_stack); + + real_t clamp_angle(real_t p_angle, real_t p_min_bound, real_t p_max_bound, bool p_invert); + + void set_enabled(bool p_enabled); + bool get_enabled(); + + void set_execution_mode(int p_mode); + int get_execution_mode() const; + + Ref<SkeletonModificationStack3D> get_modification_stack(); + + void set_is_setup(bool p_setup); + bool get_is_setup() const; + + SkeletonModification3D(); +}; + +#endif // SKELETONMODIFICATION3D_H diff --git a/scene/resources/skeleton_modification_3d_ccdik.cpp b/scene/resources/skeleton_modification_3d_ccdik.cpp new file mode 100644 index 0000000000..6409022563 --- /dev/null +++ b/scene/resources/skeleton_modification_3d_ccdik.cpp @@ -0,0 +1,474 @@ +/*************************************************************************/ +/* skeleton_modification_3d_ccdik.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "scene/resources/skeleton_modification_3d_ccdik.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/skeleton_modification_3d.h" + +bool SkeletonModification3DCCDIK::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.begins_with("joint_data/")) { + int ccdik_data_size = ccdik_data_chain.size(); + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, ccdik_data_size, false); + + if (what == "bone_name") { + set_ccdik_joint_bone_name(which, p_value); + } else if (what == "bone_index") { + set_ccdik_joint_bone_index(which, p_value); + } else if (what == "ccdik_axis") { + set_ccdik_joint_ccdik_axis(which, p_value); + } else if (what == "enable_joint_constraint") { + set_ccdik_joint_enable_constraint(which, p_value); + } else if (what == "joint_constraint_angle_min") { + set_ccdik_joint_constraint_angle_min(which, Math::deg2rad(real_t(p_value))); + } else if (what == "joint_constraint_angle_max") { + set_ccdik_joint_constraint_angle_max(which, Math::deg2rad(real_t(p_value))); + } else if (what == "joint_constraint_angles_invert") { + set_ccdik_joint_constraint_invert(which, p_value); + } + return true; + } + return true; +} + +bool SkeletonModification3DCCDIK::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.begins_with("joint_data/")) { + const int ccdik_data_size = ccdik_data_chain.size(); + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, ccdik_data_size, false); + + if (what == "bone_name") { + r_ret = get_ccdik_joint_bone_name(which); + } else if (what == "bone_index") { + r_ret = get_ccdik_joint_bone_index(which); + } else if (what == "ccdik_axis") { + r_ret = get_ccdik_joint_ccdik_axis(which); + } else if (what == "enable_joint_constraint") { + r_ret = get_ccdik_joint_enable_constraint(which); + } else if (what == "joint_constraint_angle_min") { + r_ret = Math::rad2deg(get_ccdik_joint_constraint_angle_min(which)); + } else if (what == "joint_constraint_angle_max") { + r_ret = Math::rad2deg(get_ccdik_joint_constraint_angle_max(which)); + } else if (what == "joint_constraint_angles_invert") { + r_ret = get_ccdik_joint_constraint_invert(which); + } + return true; + } + return true; +} + +void SkeletonModification3DCCDIK::_get_property_list(List<PropertyInfo> *p_list) const { + for (uint32_t i = 0; i < ccdik_data_chain.size(); i++) { + String base_string = "joint_data/" + itos(i) + "/"; + + p_list->push_back(PropertyInfo(Variant::STRING_NAME, base_string + "bone_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::INT, base_string + "bone_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + + p_list->push_back(PropertyInfo(Variant::INT, base_string + "ccdik_axis", + PROPERTY_HINT_ENUM, "X Axis, Y Axis, Z Axis", PROPERTY_USAGE_DEFAULT)); + + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "enable_joint_constraint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + if (ccdik_data_chain[i].enable_constraint) { + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "joint_constraint_angle_min", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "joint_constraint_angle_max", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "joint_constraint_angles_invert", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } + } +} + +void SkeletonModification3DCCDIK::_execute(real_t p_delta) { + ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot execute!"); + if (!enabled) { + return; + } + + if (target_node_cache.is_null()) { + _print_execution_error(true, "Target cache is out of date. Attempting to update"); + update_target_cache(); + return; + } + if (tip_node_cache.is_null()) { + _print_execution_error(true, "Tip cache is out of date. Attempting to update"); + update_tip_cache(); + return; + } + + // Reset the local bone overrides for CCDIK affected nodes + for (uint32_t i = 0; i < ccdik_data_chain.size(); i++) { + stack->skeleton->set_bone_local_pose_override(ccdik_data_chain[i].bone_idx, + stack->skeleton->get_bone_local_pose_override(ccdik_data_chain[i].bone_idx), + 0.0, false); + } + + Node3D *node_target = Object::cast_to<Node3D>(ObjectDB::get_instance(target_node_cache)); + Node3D *node_tip = Object::cast_to<Node3D>(ObjectDB::get_instance(tip_node_cache)); + + if (_print_execution_error(!node_target || !node_target->is_inside_tree(), "Target node is not in the scene tree. Cannot execute modification!")) { + return; + } + if (_print_execution_error(!node_tip || !node_tip->is_inside_tree(), "Tip node is not in the scene tree. Cannot execute modification!")) { + return; + } + + if (use_high_quality_solve) { + for (uint32_t i = 0; i < ccdik_data_chain.size(); i++) { + for (uint32_t j = i; j < ccdik_data_chain.size(); j++) { + _execute_ccdik_joint(j, node_target, node_tip); + } + } + } else { + for (uint32_t i = 0; i < ccdik_data_chain.size(); i++) { + _execute_ccdik_joint(i, node_target, node_tip); + } + } + + execution_error_found = false; +} + +void SkeletonModification3DCCDIK::_execute_ccdik_joint(int p_joint_idx, Node3D *p_target, Node3D *p_tip) { + CCDIK_Joint_Data ccdik_data = ccdik_data_chain[p_joint_idx]; + + if (_print_execution_error(ccdik_data.bone_idx < 0 || ccdik_data.bone_idx > stack->skeleton->get_bone_count(), + "CCDIK joint: bone index for joint" + itos(p_joint_idx) + " not found. Cannot execute modification!")) { + return; + } + + Transform3D bone_trans = stack->skeleton->global_pose_to_local_pose(ccdik_data.bone_idx, stack->skeleton->get_bone_global_pose(ccdik_data.bone_idx)); + Transform3D tip_trans = stack->skeleton->global_pose_to_local_pose(ccdik_data.bone_idx, stack->skeleton->world_transform_to_global_pose(p_tip->get_global_transform())); + Transform3D target_trans = stack->skeleton->global_pose_to_local_pose(ccdik_data.bone_idx, stack->skeleton->world_transform_to_global_pose(p_target->get_global_transform())); + + if (tip_trans.origin.distance_to(target_trans.origin) <= 0.01) { + return; + } + + // Inspired (and very loosely based on) by the CCDIK algorithm made by Zalo on GitHub (https://github.com/zalo/MathUtilities) + // Convert the 3D position to a 2D position so we can use Atan2 (via the angle function) + // to know how much rotation we need on the given axis to place the tip at the target. + Vector2 tip_pos_2d; + Vector2 target_pos_2d; + if (ccdik_data.ccdik_axis == CCDIK_Axes::AXIS_X) { + tip_pos_2d = Vector2(tip_trans.origin.y, tip_trans.origin.z); + target_pos_2d = Vector2(target_trans.origin.y, target_trans.origin.z); + bone_trans.basis.rotate_local(Vector3(1, 0, 0), target_pos_2d.angle() - tip_pos_2d.angle()); + } else if (ccdik_data.ccdik_axis == CCDIK_Axes::AXIS_Y) { + tip_pos_2d = Vector2(tip_trans.origin.z, tip_trans.origin.x); + target_pos_2d = Vector2(target_trans.origin.z, target_trans.origin.x); + bone_trans.basis.rotate_local(Vector3(0, 1, 0), target_pos_2d.angle() - tip_pos_2d.angle()); + } else if (ccdik_data.ccdik_axis == CCDIK_Axes::AXIS_Z) { + tip_pos_2d = Vector2(tip_trans.origin.x, tip_trans.origin.y); + target_pos_2d = Vector2(target_trans.origin.x, target_trans.origin.y); + bone_trans.basis.rotate_local(Vector3(0, 0, 1), target_pos_2d.angle() - tip_pos_2d.angle()); + } else { + // Should never happen, but... + ERR_FAIL_MSG("CCDIK joint: Unknown axis vector passed for joint" + itos(p_joint_idx) + ". Cannot execute modification!"); + } + + if (ccdik_data.enable_constraint) { + Vector3 rotation_axis; + real_t rotation_angle; + bone_trans.basis.get_axis_angle(rotation_axis, rotation_angle); + + // Note: When the axis has a negative direction, the angle is OVER 180 degrees and therefore we need to account for this + // when constraining. + if (ccdik_data.ccdik_axis == CCDIK_Axes::AXIS_X) { + if (rotation_axis.x < 0) { + rotation_angle += Math_PI; + rotation_axis = Vector3(1, 0, 0); + } + } else if (ccdik_data.ccdik_axis == CCDIK_Axes::AXIS_Y) { + if (rotation_axis.y < 0) { + rotation_angle += Math_PI; + rotation_axis = Vector3(0, 1, 0); + } + } else if (ccdik_data.ccdik_axis == CCDIK_Axes::AXIS_Z) { + if (rotation_axis.z < 0) { + rotation_angle += Math_PI; + rotation_axis = Vector3(0, 0, 1); + } + } else { + // Should never happen, but... + ERR_FAIL_MSG("CCDIK joint: Unknown axis vector passed for joint" + itos(p_joint_idx) + ". Cannot execute modification!"); + } + rotation_angle = clamp_angle(rotation_angle, ccdik_data.constraint_angle_min, ccdik_data.constraint_angle_max, ccdik_data.constraint_angles_invert); + + bone_trans.basis.set_axis_angle(rotation_axis, rotation_angle); + } + + stack->skeleton->set_bone_local_pose_override(ccdik_data.bone_idx, bone_trans, stack->strength, true); + stack->skeleton->force_update_bone_children_transforms(ccdik_data.bone_idx); +} + +void SkeletonModification3DCCDIK::_setup_modification(SkeletonModificationStack3D *p_stack) { + stack = p_stack; + if (stack != nullptr) { + is_setup = true; + execution_error_found = false; + update_target_cache(); + update_tip_cache(); + } +} + +void SkeletonModification3DCCDIK::update_target_cache() { + if (!is_setup || !stack) { + _print_execution_error(true, "Cannot update target cache: modification is not properly setup!"); + return; + } + + target_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(target_node)) { + Node *node = stack->skeleton->get_node(target_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update target cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update target cache: node is not in scene tree!"); + target_node_cache = node->get_instance_id(); + + execution_error_found = false; + } + } + } +} + +void SkeletonModification3DCCDIK::update_tip_cache() { + if (!is_setup || !stack) { + _print_execution_error(true, "Cannot update tip cache: modification is not properly setup!"); + return; + } + + tip_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(tip_node)) { + Node *node = stack->skeleton->get_node(tip_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update tip cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update tip cache: node is not in scene tree!"); + tip_node_cache = node->get_instance_id(); + + execution_error_found = false; + } + } + } +} + +void SkeletonModification3DCCDIK::set_target_node(const NodePath &p_target_node) { + target_node = p_target_node; + update_target_cache(); +} + +NodePath SkeletonModification3DCCDIK::get_target_node() const { + return target_node; +} + +void SkeletonModification3DCCDIK::set_tip_node(const NodePath &p_tip_node) { + tip_node = p_tip_node; + update_tip_cache(); +} + +NodePath SkeletonModification3DCCDIK::get_tip_node() const { + return tip_node; +} + +void SkeletonModification3DCCDIK::set_use_high_quality_solve(bool p_high_quality) { + use_high_quality_solve = p_high_quality; +} + +bool SkeletonModification3DCCDIK::get_use_high_quality_solve() const { + return use_high_quality_solve; +} + +// CCDIK joint data functions +String SkeletonModification3DCCDIK::get_ccdik_joint_bone_name(int p_joint_idx) const { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, String()); + return ccdik_data_chain[p_joint_idx].bone_name; +} + +void SkeletonModification3DCCDIK::set_ccdik_joint_bone_name(int p_joint_idx, String p_bone_name) { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + ccdik_data_chain[p_joint_idx].bone_name = p_bone_name; + + if (stack) { + if (stack->skeleton) { + ccdik_data_chain[p_joint_idx].bone_idx = stack->skeleton->find_bone(p_bone_name); + } + } + execution_error_found = false; + notify_property_list_changed(); +} + +int SkeletonModification3DCCDIK::get_ccdik_joint_bone_index(int p_joint_idx) const { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, -1); + return ccdik_data_chain[p_joint_idx].bone_idx; +} + +void SkeletonModification3DCCDIK::set_ccdik_joint_bone_index(int p_joint_idx, int p_bone_idx) { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); + ccdik_data_chain[p_joint_idx].bone_idx = p_bone_idx; + + if (stack) { + if (stack->skeleton) { + ccdik_data_chain[p_joint_idx].bone_name = stack->skeleton->get_bone_name(p_bone_idx); + } + } + execution_error_found = false; + notify_property_list_changed(); +} + +int SkeletonModification3DCCDIK::get_ccdik_joint_ccdik_axis(int p_joint_idx) const { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, -1); + return ccdik_data_chain[p_joint_idx].ccdik_axis; +} + +void SkeletonModification3DCCDIK::set_ccdik_joint_ccdik_axis(int p_joint_idx, int p_axis) { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + ERR_FAIL_COND_MSG(p_axis < 0, "CCDIK axis is out of range: The axis mode is too low!"); + ccdik_data_chain[p_joint_idx].ccdik_axis = p_axis; + notify_property_list_changed(); +} + +bool SkeletonModification3DCCDIK::get_ccdik_joint_enable_constraint(int p_joint_idx) const { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); + return ccdik_data_chain[p_joint_idx].enable_constraint; +} + +void SkeletonModification3DCCDIK::set_ccdik_joint_enable_constraint(int p_joint_idx, bool p_enable) { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + ccdik_data_chain[p_joint_idx].enable_constraint = p_enable; + notify_property_list_changed(); +} + +real_t SkeletonModification3DCCDIK::get_ccdik_joint_constraint_angle_min(int p_joint_idx) const { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); + return ccdik_data_chain[p_joint_idx].constraint_angle_min; +} + +void SkeletonModification3DCCDIK::set_ccdik_joint_constraint_angle_min(int p_joint_idx, real_t p_angle_min) { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + ccdik_data_chain[p_joint_idx].constraint_angle_min = p_angle_min; +} + +real_t SkeletonModification3DCCDIK::get_ccdik_joint_constraint_angle_max(int p_joint_idx) const { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); + return ccdik_data_chain[p_joint_idx].constraint_angle_max; +} + +void SkeletonModification3DCCDIK::set_ccdik_joint_constraint_angle_max(int p_joint_idx, real_t p_angle_max) { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + ccdik_data_chain[p_joint_idx].constraint_angle_max = p_angle_max; +} + +bool SkeletonModification3DCCDIK::get_ccdik_joint_constraint_invert(int p_joint_idx) const { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); + return ccdik_data_chain[p_joint_idx].constraint_angles_invert; +} + +void SkeletonModification3DCCDIK::set_ccdik_joint_constraint_invert(int p_joint_idx, bool p_invert) { + const int bone_chain_size = ccdik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + ccdik_data_chain[p_joint_idx].constraint_angles_invert = p_invert; +} + +int SkeletonModification3DCCDIK::get_ccdik_data_chain_length() { + return ccdik_data_chain.size(); +} +void SkeletonModification3DCCDIK::set_ccdik_data_chain_length(int p_length) { + ERR_FAIL_COND(p_length < 0); + ccdik_data_chain.resize(p_length); + execution_error_found = false; + notify_property_list_changed(); +} + +void SkeletonModification3DCCDIK::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification3DCCDIK::set_target_node); + ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification3DCCDIK::get_target_node); + + ClassDB::bind_method(D_METHOD("set_tip_node", "tip_nodepath"), &SkeletonModification3DCCDIK::set_tip_node); + ClassDB::bind_method(D_METHOD("get_tip_node"), &SkeletonModification3DCCDIK::get_tip_node); + + ClassDB::bind_method(D_METHOD("set_use_high_quality_solve", "high_quality_solve"), &SkeletonModification3DCCDIK::set_use_high_quality_solve); + ClassDB::bind_method(D_METHOD("get_use_high_quality_solve"), &SkeletonModification3DCCDIK::get_use_high_quality_solve); + + // CCDIK joint data functions + ClassDB::bind_method(D_METHOD("get_ccdik_joint_bone_name", "joint_idx"), &SkeletonModification3DCCDIK::get_ccdik_joint_bone_name); + ClassDB::bind_method(D_METHOD("set_ccdik_joint_bone_name", "joint_idx", "bone_name"), &SkeletonModification3DCCDIK::set_ccdik_joint_bone_name); + ClassDB::bind_method(D_METHOD("get_ccdik_joint_bone_index", "joint_idx"), &SkeletonModification3DCCDIK::get_ccdik_joint_bone_index); + ClassDB::bind_method(D_METHOD("set_ccdik_joint_bone_index", "joint_idx", "bone_index"), &SkeletonModification3DCCDIK::set_ccdik_joint_bone_index); + ClassDB::bind_method(D_METHOD("get_ccdik_joint_ccdik_axis", "joint_idx"), &SkeletonModification3DCCDIK::get_ccdik_joint_ccdik_axis); + ClassDB::bind_method(D_METHOD("set_ccdik_joint_ccdik_axis", "joint_idx", "axis"), &SkeletonModification3DCCDIK::set_ccdik_joint_ccdik_axis); + ClassDB::bind_method(D_METHOD("get_ccdik_joint_enable_joint_constraint", "joint_idx"), &SkeletonModification3DCCDIK::get_ccdik_joint_enable_constraint); + ClassDB::bind_method(D_METHOD("set_ccdik_joint_enable_joint_constraint", "joint_idx", "enable"), &SkeletonModification3DCCDIK::set_ccdik_joint_enable_constraint); + ClassDB::bind_method(D_METHOD("get_ccdik_joint_constraint_angle_min", "joint_idx"), &SkeletonModification3DCCDIK::get_ccdik_joint_constraint_angle_min); + ClassDB::bind_method(D_METHOD("set_ccdik_joint_constraint_angle_min", "joint_idx", "min_angle"), &SkeletonModification3DCCDIK::set_ccdik_joint_constraint_angle_min); + ClassDB::bind_method(D_METHOD("get_ccdik_joint_constraint_angle_max", "joint_idx"), &SkeletonModification3DCCDIK::get_ccdik_joint_constraint_angle_max); + ClassDB::bind_method(D_METHOD("set_ccdik_joint_constraint_angle_max", "joint_idx", "max_angle"), &SkeletonModification3DCCDIK::set_ccdik_joint_constraint_angle_max); + ClassDB::bind_method(D_METHOD("get_ccdik_joint_constraint_invert", "joint_idx"), &SkeletonModification3DCCDIK::get_ccdik_joint_constraint_invert); + ClassDB::bind_method(D_METHOD("set_ccdik_joint_constraint_invert", "joint_idx", "invert"), &SkeletonModification3DCCDIK::set_ccdik_joint_constraint_invert); + + ClassDB::bind_method(D_METHOD("set_ccdik_data_chain_length", "length"), &SkeletonModification3DCCDIK::set_ccdik_data_chain_length); + ClassDB::bind_method(D_METHOD("get_ccdik_data_chain_length"), &SkeletonModification3DCCDIK::get_ccdik_data_chain_length); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_target_node", "get_target_node"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "tip_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_tip_node", "get_tip_node"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "high_quality_solve", PROPERTY_HINT_NONE, ""), "set_use_high_quality_solve", "get_use_high_quality_solve"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "ccdik_data_chain_length", PROPERTY_HINT_RANGE, "0,100,1"), "set_ccdik_data_chain_length", "get_ccdik_data_chain_length"); +} + +SkeletonModification3DCCDIK::SkeletonModification3DCCDIK() { + stack = nullptr; + is_setup = false; + enabled = true; +} + +SkeletonModification3DCCDIK::~SkeletonModification3DCCDIK() { +} diff --git a/scene/resources/skeleton_modification_3d_ccdik.h b/scene/resources/skeleton_modification_3d_ccdik.h new file mode 100644 index 0000000000..e7537cc5b0 --- /dev/null +++ b/scene/resources/skeleton_modification_3d_ccdik.h @@ -0,0 +1,114 @@ +/*************************************************************************/ +/* skeleton_modification_3d_ccdik.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "core/templates/local_vector.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/skeleton_modification_3d.h" + +#ifndef SKELETONMODIFICATION3DCCDIK_H +#define SKELETONMODIFICATION3DCCDIK_H + +class SkeletonModification3DCCDIK : public SkeletonModification3D { + GDCLASS(SkeletonModification3DCCDIK, SkeletonModification3D); + +private: + enum CCDIK_Axes { + AXIS_X, + AXIS_Y, + AXIS_Z + }; + + struct CCDIK_Joint_Data { + String bone_name = ""; + int bone_idx = -1; + int ccdik_axis = 0; + + bool enable_constraint = false; + real_t constraint_angle_min = 0; + real_t constraint_angle_max = (2.0 * Math_PI); + bool constraint_angles_invert = false; + }; + + LocalVector<CCDIK_Joint_Data> ccdik_data_chain; + NodePath target_node; + ObjectID target_node_cache; + + NodePath tip_node; + ObjectID tip_node_cache; + + bool use_high_quality_solve = true; + + void update_target_cache(); + void update_tip_cache(); + + void _execute_ccdik_joint(int p_joint_idx, Node3D *p_target, Node3D *p_tip); + +protected: + static void _bind_methods(); + bool _get(const StringName &p_path, Variant &r_ret) const; + bool _set(const StringName &p_path, const Variant &p_value); + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + virtual void _execute(real_t p_delta) override; + virtual void _setup_modification(SkeletonModificationStack3D *p_stack) override; + + void set_target_node(const NodePath &p_target_node); + NodePath get_target_node() const; + + void set_tip_node(const NodePath &p_tip_node); + NodePath get_tip_node() const; + + void set_use_high_quality_solve(bool p_solve); + bool get_use_high_quality_solve() const; + + String get_ccdik_joint_bone_name(int p_joint_idx) const; + void set_ccdik_joint_bone_name(int p_joint_idx, String p_bone_name); + int get_ccdik_joint_bone_index(int p_joint_idx) const; + void set_ccdik_joint_bone_index(int p_joint_idx, int p_bone_idx); + int get_ccdik_joint_ccdik_axis(int p_joint_idx) const; + void set_ccdik_joint_ccdik_axis(int p_joint_idx, int p_axis); + bool get_ccdik_joint_enable_constraint(int p_joint_idx) const; + void set_ccdik_joint_enable_constraint(int p_joint_idx, bool p_enable); + real_t get_ccdik_joint_constraint_angle_min(int p_joint_idx) const; + void set_ccdik_joint_constraint_angle_min(int p_joint_idx, real_t p_angle_min); + real_t get_ccdik_joint_constraint_angle_max(int p_joint_idx) const; + void set_ccdik_joint_constraint_angle_max(int p_joint_idx, real_t p_angle_max); + bool get_ccdik_joint_constraint_invert(int p_joint_idx) const; + void set_ccdik_joint_constraint_invert(int p_joint_idx, bool p_invert); + + int get_ccdik_data_chain_length(); + void set_ccdik_data_chain_length(int p_new_length); + + SkeletonModification3DCCDIK(); + ~SkeletonModification3DCCDIK(); +}; + +#endif //SKELETONMODIFICATION3DCCDIK_H diff --git a/scene/resources/skeleton_modification_3d_fabrik.cpp b/scene/resources/skeleton_modification_3d_fabrik.cpp new file mode 100644 index 0000000000..69f75eb7b5 --- /dev/null +++ b/scene/resources/skeleton_modification_3d_fabrik.cpp @@ -0,0 +1,628 @@ +/*************************************************************************/ +/* skeleton_modification_3d_fabrik.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "scene/resources/skeleton_modification_3d_fabrik.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/skeleton_modification_3d.h" + +bool SkeletonModification3DFABRIK::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.begins_with("joint_data/")) { + int fabrik_data_size = fabrik_data_chain.size(); + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, fabrik_data_size, false); + + if (what == "bone_name") { + set_fabrik_joint_bone_name(which, p_value); + } else if (what == "bone_index") { + set_fabrik_joint_bone_index(which, p_value); + } else if (what == "length") { + set_fabrik_joint_length(which, p_value); + } else if (what == "magnet_position") { + set_fabrik_joint_magnet(which, p_value); + } else if (what == "auto_calculate_length") { + set_fabrik_joint_auto_calculate_length(which, p_value); + } else if (what == "use_tip_node") { + set_fabrik_joint_use_tip_node(which, p_value); + } else if (what == "tip_node") { + set_fabrik_joint_tip_node(which, p_value); + } else if (what == "use_target_basis") { + set_fabrik_joint_use_target_basis(which, p_value); + } else if (what == "roll") { + set_fabrik_joint_roll(which, Math::deg2rad(real_t(p_value))); + } + return true; + } + return true; +} + +bool SkeletonModification3DFABRIK::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.begins_with("joint_data/")) { + const int fabrik_data_size = fabrik_data_chain.size(); + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, fabrik_data_size, false); + + if (what == "bone_name") { + r_ret = get_fabrik_joint_bone_name(which); + } else if (what == "bone_index") { + r_ret = get_fabrik_joint_bone_index(which); + } else if (what == "length") { + r_ret = get_fabrik_joint_length(which); + } else if (what == "magnet_position") { + r_ret = get_fabrik_joint_magnet(which); + } else if (what == "auto_calculate_length") { + r_ret = get_fabrik_joint_auto_calculate_length(which); + } else if (what == "use_tip_node") { + r_ret = get_fabrik_joint_use_tip_node(which); + } else if (what == "tip_node") { + r_ret = get_fabrik_joint_tip_node(which); + } else if (what == "use_target_basis") { + r_ret = get_fabrik_joint_use_target_basis(which); + } else if (what == "roll") { + r_ret = Math::rad2deg(get_fabrik_joint_roll(which)); + } + return true; + } + return true; +} + +void SkeletonModification3DFABRIK::_get_property_list(List<PropertyInfo> *p_list) const { + for (uint32_t i = 0; i < fabrik_data_chain.size(); i++) { + String base_string = "joint_data/" + itos(i) + "/"; + + p_list->push_back(PropertyInfo(Variant::STRING_NAME, base_string + "bone_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::INT, base_string + "bone_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "roll", PROPERTY_HINT_RANGE, "-360,360,0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "auto_calculate_length", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + + if (!fabrik_data_chain[i].auto_calculate_length) { + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "length", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } else { + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "use_tip_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + if (fabrik_data_chain[i].use_tip_node) { + p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "tip_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D", PROPERTY_USAGE_DEFAULT)); + } + } + + // Cannot apply magnet to the origin of the chain, as it will not do anything. + if (i > 0) { + p_list->push_back(PropertyInfo(Variant::VECTOR3, base_string + "magnet_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } + // Only give the override basis option on the last bone in the chain, so only include it for the last bone. + if (i == fabrik_data_chain.size() - 1) { + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "use_target_basis", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } + } +} + +void SkeletonModification3DFABRIK::_execute(real_t p_delta) { + ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot execute!"); + if (!enabled) { + return; + } + + if (target_node_cache.is_null()) { + _print_execution_error(true, "Target cache is out of date. Attempting to update..."); + update_target_cache(); + return; + } + + if (_print_execution_error(fabrik_data_chain.size() <= 1, "FABRIK requires at least two joints to operate. Cannot execute modification!")) { + return; + } + + Node3D *node_target = Object::cast_to<Node3D>(ObjectDB::get_instance(target_node_cache)); + if (_print_execution_error(!node_target || !node_target->is_inside_tree(), "Target node is not in the scene tree. Cannot execute modification!")) { + return; + } + + // Verify that all joints have a valid bone ID, and that all bone lengths are zero or more + // Also, while we are here, apply magnet positions. + for (uint32_t i = 0; i < fabrik_data_chain.size(); i++) { + if (_print_execution_error(fabrik_data_chain[i].bone_idx < 0, "FABRIK Joint " + itos(i) + " has an invalid bone ID. Cannot execute!")) { + return; + } + + if (fabrik_data_chain[i].length < 0 && fabrik_data_chain[i].auto_calculate_length) { + fabrik_joint_auto_calculate_length(i); + } + if (_print_execution_error(fabrik_data_chain[i].length < 0, "FABRIK Joint " + itos(i) + " has an invalid joint length. Cannot execute!")) { + return; + } + + Transform3D local_pose_override = stack->skeleton->get_bone_local_pose_override(fabrik_data_chain[i].bone_idx); + + // Apply magnet positions: + if (stack->skeleton->get_bone_parent(fabrik_data_chain[i].bone_idx) >= 0) { + int parent_bone_idx = stack->skeleton->get_bone_parent(fabrik_data_chain[i].bone_idx); + Transform3D conversion_transform = (stack->skeleton->get_bone_global_pose(parent_bone_idx) * stack->skeleton->get_bone_rest(parent_bone_idx)); + local_pose_override.origin += conversion_transform.basis.xform_inv(fabrik_data_chain[i].magnet_position); + } else { + local_pose_override.origin += fabrik_data_chain[i].magnet_position; + } + + stack->skeleton->set_bone_local_pose_override(fabrik_data_chain[i].bone_idx, local_pose_override, stack->strength, true); + } + + target_global_pose = stack->skeleton->world_transform_to_global_pose(node_target->get_global_transform()); + origin_global_pose = stack->skeleton->local_pose_to_global_pose( + fabrik_data_chain[0].bone_idx, stack->skeleton->get_bone_local_pose_override(fabrik_data_chain[0].bone_idx)); + + final_joint_idx = fabrik_data_chain.size() - 1; + real_t target_distance = stack->skeleton->global_pose_to_local_pose(fabrik_data_chain[final_joint_idx].bone_idx, target_global_pose).origin.length(); + chain_iterations = 0; + + while (target_distance > chain_tolerance) { + chain_backwards(); + chain_forwards(); + + // update the target distance + target_distance = stack->skeleton->global_pose_to_local_pose(fabrik_data_chain[final_joint_idx].bone_idx, target_global_pose).origin.length(); + + // update chain iterations + chain_iterations += 1; + if (chain_iterations >= chain_max_iterations) { + break; + } + } + chain_apply(); + + execution_error_found = false; +} + +void SkeletonModification3DFABRIK::chain_backwards() { + int final_bone_idx = fabrik_data_chain[final_joint_idx].bone_idx; + Transform3D final_joint_trans = stack->skeleton->local_pose_to_global_pose(final_bone_idx, stack->skeleton->get_bone_local_pose_override(final_bone_idx)); + + // Get the direction the final bone is facing in. + stack->skeleton->update_bone_rest_forward_vector(final_bone_idx); + Transform3D final_bone_direction_trans = final_joint_trans.looking_at(target_global_pose.origin, Vector3(0, 1, 0)); + final_bone_direction_trans.basis = stack->skeleton->global_pose_z_forward_to_bone_forward(final_bone_idx, final_bone_direction_trans.basis); + Vector3 direction = final_bone_direction_trans.basis.xform(stack->skeleton->get_bone_axis_forward_vector(final_bone_idx)).normalized(); + + // If set to override, then use the target's Basis rather than the bone's + if (fabrik_data_chain[final_joint_idx].use_target_basis) { + direction = target_global_pose.basis.xform(stack->skeleton->get_bone_axis_forward_vector(final_bone_idx)).normalized(); + } + + // set the position of the final joint to the target position + final_joint_trans.origin = target_global_pose.origin - (direction * fabrik_data_chain[final_joint_idx].length); + final_joint_trans = stack->skeleton->global_pose_to_local_pose(final_bone_idx, final_joint_trans); + stack->skeleton->set_bone_local_pose_override(final_bone_idx, final_joint_trans, stack->strength, true); + + // for all other joints, move them towards the target + int i = final_joint_idx; + while (i >= 1) { + int next_bone_idx = fabrik_data_chain[i].bone_idx; + Transform3D next_bone_trans = stack->skeleton->local_pose_to_global_pose(next_bone_idx, stack->skeleton->get_bone_local_pose_override(next_bone_idx)); + i -= 1; + int current_bone_idx = fabrik_data_chain[i].bone_idx; + Transform3D current_trans = stack->skeleton->local_pose_to_global_pose(current_bone_idx, stack->skeleton->get_bone_local_pose_override(current_bone_idx)); + + real_t length = fabrik_data_chain[i].length / (next_bone_trans.origin - current_trans.origin).length(); + current_trans.origin = next_bone_trans.origin.lerp(current_trans.origin, length); + + // Apply it back to the skeleton + stack->skeleton->set_bone_local_pose_override(current_bone_idx, stack->skeleton->global_pose_to_local_pose(current_bone_idx, current_trans), stack->strength, true); + } +} + +void SkeletonModification3DFABRIK::chain_forwards() { + // Set root at the initial position. + int origin_bone_idx = fabrik_data_chain[0].bone_idx; + Transform3D root_transform = stack->skeleton->local_pose_to_global_pose(origin_bone_idx, stack->skeleton->get_bone_local_pose_override(origin_bone_idx)); + root_transform.origin = origin_global_pose.origin; + stack->skeleton->set_bone_local_pose_override(origin_bone_idx, stack->skeleton->global_pose_to_local_pose(origin_bone_idx, root_transform), stack->strength, true); + + for (uint32_t i = 0; i < fabrik_data_chain.size() - 1; i++) { + int current_bone_idx = fabrik_data_chain[i].bone_idx; + Transform3D current_trans = stack->skeleton->local_pose_to_global_pose(current_bone_idx, stack->skeleton->get_bone_local_pose_override(current_bone_idx)); + int next_bone_idx = fabrik_data_chain[i + 1].bone_idx; + Transform3D next_bone_trans = stack->skeleton->local_pose_to_global_pose(next_bone_idx, stack->skeleton->get_bone_local_pose_override(next_bone_idx)); + + real_t length = fabrik_data_chain[i].length / (current_trans.origin - next_bone_trans.origin).length(); + next_bone_trans.origin = current_trans.origin.lerp(next_bone_trans.origin, length); + + // Apply it back to the skeleton + stack->skeleton->set_bone_local_pose_override(next_bone_idx, stack->skeleton->global_pose_to_local_pose(next_bone_idx, next_bone_trans), stack->strength, true); + } +} + +void SkeletonModification3DFABRIK::chain_apply() { + for (uint32_t i = 0; i < fabrik_data_chain.size(); i++) { + int current_bone_idx = fabrik_data_chain[i].bone_idx; + Transform3D current_trans = stack->skeleton->get_bone_local_pose_override(current_bone_idx); + current_trans = stack->skeleton->local_pose_to_global_pose(current_bone_idx, current_trans); + + // If this is the last bone in the chain... + if (i == fabrik_data_chain.size() - 1) { + if (fabrik_data_chain[i].use_target_basis == false) { // Point to target... + // Get the forward direction that the basis is facing in right now. + stack->skeleton->update_bone_rest_forward_vector(current_bone_idx); + Vector3 forward_vector = stack->skeleton->get_bone_axis_forward_vector(current_bone_idx); + // Rotate the bone towards the target: + current_trans.basis.rotate_to_align(forward_vector, current_trans.origin.direction_to(target_global_pose.origin)); + current_trans.basis.rotate_local(forward_vector, fabrik_data_chain[i].roll); + } else { // Use the target's Basis... + current_trans.basis = target_global_pose.basis.orthonormalized().scaled(current_trans.basis.get_scale()); + } + } else { // every other bone in the chain... + int next_bone_idx = fabrik_data_chain[i + 1].bone_idx; + Transform3D next_trans = stack->skeleton->local_pose_to_global_pose(next_bone_idx, stack->skeleton->get_bone_local_pose_override(next_bone_idx)); + + // Get the forward direction that the basis is facing in right now. + stack->skeleton->update_bone_rest_forward_vector(current_bone_idx); + Vector3 forward_vector = stack->skeleton->get_bone_axis_forward_vector(current_bone_idx); + // Rotate the bone towards the next bone in the chain: + current_trans.basis.rotate_to_align(forward_vector, current_trans.origin.direction_to(next_trans.origin)); + current_trans.basis.rotate_local(forward_vector, fabrik_data_chain[i].roll); + } + current_trans = stack->skeleton->global_pose_to_local_pose(current_bone_idx, current_trans); + current_trans.origin = Vector3(0, 0, 0); + stack->skeleton->set_bone_local_pose_override(current_bone_idx, current_trans, stack->strength, true); + } + + // Update all the bones so the next modification has up-to-date data. + stack->skeleton->force_update_all_bone_transforms(); +} + +void SkeletonModification3DFABRIK::_setup_modification(SkeletonModificationStack3D *p_stack) { + stack = p_stack; + if (stack != nullptr) { + is_setup = true; + execution_error_found = false; + update_target_cache(); + + for (uint32_t i = 0; i < fabrik_data_chain.size(); i++) { + update_joint_tip_cache(i); + } + } +} + +void SkeletonModification3DFABRIK::update_target_cache() { + if (!is_setup || !stack) { + _print_execution_error(true, "Cannot update target cache: modification is not properly setup!"); + return; + } + target_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree() && target_node.is_empty() == false) { + if (stack->skeleton->has_node(target_node)) { + Node *node = stack->skeleton->get_node(target_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update target cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update target cache: node is not in the scene tree!"); + target_node_cache = node->get_instance_id(); + + execution_error_found = false; + } + } + } +} + +void SkeletonModification3DFABRIK::update_joint_tip_cache(int p_joint_idx) { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX_MSG(p_joint_idx, bone_chain_size, "FABRIK joint not found"); + if (!is_setup || !stack) { + _print_execution_error(true, "Cannot update tip cache: modification is not properly setup!"); + return; + } + fabrik_data_chain[p_joint_idx].tip_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree() && fabrik_data_chain[p_joint_idx].tip_node.is_empty() == false) { + if (stack->skeleton->has_node(fabrik_data_chain[p_joint_idx].tip_node)) { + Node *node = stack->skeleton->get_node(fabrik_data_chain[p_joint_idx].tip_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update tip cache for joint " + itos(p_joint_idx) + ": node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update tip cache for joint " + itos(p_joint_idx) + ": node is not in scene tree!"); + fabrik_data_chain[p_joint_idx].tip_node_cache = node->get_instance_id(); + + execution_error_found = false; + } + } + } +} + +void SkeletonModification3DFABRIK::set_target_node(const NodePath &p_target_node) { + target_node = p_target_node; + update_target_cache(); +} + +NodePath SkeletonModification3DFABRIK::get_target_node() const { + return target_node; +} + +int SkeletonModification3DFABRIK::get_fabrik_data_chain_length() { + return fabrik_data_chain.size(); +} + +void SkeletonModification3DFABRIK::set_fabrik_data_chain_length(int p_length) { + ERR_FAIL_COND(p_length < 0); + fabrik_data_chain.resize(p_length); + execution_error_found = false; + notify_property_list_changed(); +} + +real_t SkeletonModification3DFABRIK::get_chain_tolerance() { + return chain_tolerance; +} + +void SkeletonModification3DFABRIK::set_chain_tolerance(real_t p_tolerance) { + ERR_FAIL_COND_MSG(p_tolerance <= 0, "FABRIK chain tolerance must be more than zero!"); + chain_tolerance = p_tolerance; +} + +int SkeletonModification3DFABRIK::get_chain_max_iterations() { + return chain_max_iterations; +} +void SkeletonModification3DFABRIK::set_chain_max_iterations(int p_iterations) { + ERR_FAIL_COND_MSG(p_iterations <= 0, "FABRIK chain iterations must be at least one. Set enabled to false to disable the FABRIK chain."); + chain_max_iterations = p_iterations; +} + +// FABRIK joint data functions +String SkeletonModification3DFABRIK::get_fabrik_joint_bone_name(int p_joint_idx) const { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, String()); + return fabrik_data_chain[p_joint_idx].bone_name; +} + +void SkeletonModification3DFABRIK::set_fabrik_joint_bone_name(int p_joint_idx, String p_bone_name) { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + fabrik_data_chain[p_joint_idx].bone_name = p_bone_name; + + if (stack) { + if (stack->skeleton) { + fabrik_data_chain[p_joint_idx].bone_idx = stack->skeleton->find_bone(p_bone_name); + } + } + execution_error_found = false; + notify_property_list_changed(); +} + +int SkeletonModification3DFABRIK::get_fabrik_joint_bone_index(int p_joint_idx) const { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, -1); + return fabrik_data_chain[p_joint_idx].bone_idx; +} + +void SkeletonModification3DFABRIK::set_fabrik_joint_bone_index(int p_joint_idx, int p_bone_idx) { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); + fabrik_data_chain[p_joint_idx].bone_idx = p_bone_idx; + + if (stack) { + if (stack->skeleton) { + fabrik_data_chain[p_joint_idx].bone_name = stack->skeleton->get_bone_name(p_bone_idx); + } + } + execution_error_found = false; + notify_property_list_changed(); +} + +real_t SkeletonModification3DFABRIK::get_fabrik_joint_length(int p_joint_idx) const { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, -1); + return fabrik_data_chain[p_joint_idx].length; +} + +void SkeletonModification3DFABRIK::set_fabrik_joint_length(int p_joint_idx, real_t p_bone_length) { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + ERR_FAIL_COND_MSG(p_bone_length < 0, "FABRIK joint length cannot be less than zero!"); + + if (!is_setup) { + fabrik_data_chain[p_joint_idx].length = p_bone_length; + return; + } + + if (fabrik_data_chain[p_joint_idx].auto_calculate_length) { + WARN_PRINT("FABRIK Length not set: auto calculate length is enabled for this joint!"); + fabrik_joint_auto_calculate_length(p_joint_idx); + } else { + fabrik_data_chain[p_joint_idx].length = p_bone_length; + } + + execution_error_found = false; +} + +Vector3 SkeletonModification3DFABRIK::get_fabrik_joint_magnet(int p_joint_idx) const { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, Vector3()); + return fabrik_data_chain[p_joint_idx].magnet_position; +} + +void SkeletonModification3DFABRIK::set_fabrik_joint_magnet(int p_joint_idx, Vector3 p_magnet) { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + fabrik_data_chain[p_joint_idx].magnet_position = p_magnet; +} + +bool SkeletonModification3DFABRIK::get_fabrik_joint_auto_calculate_length(int p_joint_idx) const { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); + return fabrik_data_chain[p_joint_idx].auto_calculate_length; +} + +void SkeletonModification3DFABRIK::set_fabrik_joint_auto_calculate_length(int p_joint_idx, bool p_auto_calculate) { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + fabrik_data_chain[p_joint_idx].auto_calculate_length = p_auto_calculate; + fabrik_joint_auto_calculate_length(p_joint_idx); + notify_property_list_changed(); +} + +void SkeletonModification3DFABRIK::fabrik_joint_auto_calculate_length(int p_joint_idx) { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + if (!fabrik_data_chain[p_joint_idx].auto_calculate_length) { + return; + } + + if (!stack || !stack->skeleton || !is_setup) { + _print_execution_error(true, "Cannot auto calculate joint length: modification is not properly setup!"); + return; + } + ERR_FAIL_INDEX_MSG(fabrik_data_chain[p_joint_idx].bone_idx, stack->skeleton->get_bone_count(), + "Bone for joint " + itos(p_joint_idx) + " is not set or points to an unknown bone!"); + + if (fabrik_data_chain[p_joint_idx].use_tip_node) { // Use the tip node to update joint length. + + update_joint_tip_cache(p_joint_idx); + + Node3D *tip_node = Object::cast_to<Node3D>(ObjectDB::get_instance(fabrik_data_chain[p_joint_idx].tip_node_cache)); + ERR_FAIL_COND_MSG(!tip_node, "Tip node for joint " + itos(p_joint_idx) + "is not a Node3D-based node. Cannot calculate length..."); + ERR_FAIL_COND_MSG(!tip_node->is_inside_tree(), "Tip node for joint " + itos(p_joint_idx) + "is not in the scene tree. Cannot calculate length..."); + + Transform3D node_trans = tip_node->get_global_transform(); + node_trans = stack->skeleton->world_transform_to_global_pose(node_trans); + node_trans = stack->skeleton->global_pose_to_local_pose(fabrik_data_chain[p_joint_idx].bone_idx, node_trans); + fabrik_data_chain[p_joint_idx].length = node_trans.origin.length(); + } else { // Use child bone(s) to update joint length, if possible + Vector<int> bone_children = stack->skeleton->get_bone_children(fabrik_data_chain[p_joint_idx].bone_idx); + if (bone_children.size() <= 0) { + ERR_FAIL_MSG("Cannot calculate length for joint " + itos(p_joint_idx) + "joint uses leaf bone. \nPlease manually set the bone length or use a tip node!"); + return; + } + + real_t final_length = 0; + for (int i = 0; i < bone_children.size(); i++) { + Transform3D child_transform = stack->skeleton->get_bone_global_pose(bone_children[i]); + final_length += stack->skeleton->global_pose_to_local_pose(fabrik_data_chain[p_joint_idx].bone_idx, child_transform).origin.length(); + } + fabrik_data_chain[p_joint_idx].length = final_length / bone_children.size(); + } + execution_error_found = false; + notify_property_list_changed(); +} + +bool SkeletonModification3DFABRIK::get_fabrik_joint_use_tip_node(int p_joint_idx) const { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); + return fabrik_data_chain[p_joint_idx].use_tip_node; +} + +void SkeletonModification3DFABRIK::set_fabrik_joint_use_tip_node(int p_joint_idx, bool p_use_tip_node) { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + fabrik_data_chain[p_joint_idx].use_tip_node = p_use_tip_node; + notify_property_list_changed(); +} + +NodePath SkeletonModification3DFABRIK::get_fabrik_joint_tip_node(int p_joint_idx) const { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, NodePath()); + return fabrik_data_chain[p_joint_idx].tip_node; +} + +void SkeletonModification3DFABRIK::set_fabrik_joint_tip_node(int p_joint_idx, NodePath p_tip_node) { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + fabrik_data_chain[p_joint_idx].tip_node = p_tip_node; + update_joint_tip_cache(p_joint_idx); +} + +bool SkeletonModification3DFABRIK::get_fabrik_joint_use_target_basis(int p_joint_idx) const { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); + return fabrik_data_chain[p_joint_idx].use_target_basis; +} + +void SkeletonModification3DFABRIK::set_fabrik_joint_use_target_basis(int p_joint_idx, bool p_use_target_basis) { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + fabrik_data_chain[p_joint_idx].use_target_basis = p_use_target_basis; +} + +real_t SkeletonModification3DFABRIK::get_fabrik_joint_roll(int p_joint_idx) const { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, 0.0); + return fabrik_data_chain[p_joint_idx].roll; +} + +void SkeletonModification3DFABRIK::set_fabrik_joint_roll(int p_joint_idx, real_t p_roll) { + const int bone_chain_size = fabrik_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + fabrik_data_chain[p_joint_idx].roll = p_roll; +} + +void SkeletonModification3DFABRIK::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification3DFABRIK::set_target_node); + ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification3DFABRIK::get_target_node); + ClassDB::bind_method(D_METHOD("set_fabrik_data_chain_length", "length"), &SkeletonModification3DFABRIK::set_fabrik_data_chain_length); + ClassDB::bind_method(D_METHOD("get_fabrik_data_chain_length"), &SkeletonModification3DFABRIK::get_fabrik_data_chain_length); + ClassDB::bind_method(D_METHOD("set_chain_tolerance", "tolerance"), &SkeletonModification3DFABRIK::set_chain_tolerance); + ClassDB::bind_method(D_METHOD("get_chain_tolerance"), &SkeletonModification3DFABRIK::get_chain_tolerance); + ClassDB::bind_method(D_METHOD("set_chain_max_iterations", "max_iterations"), &SkeletonModification3DFABRIK::set_chain_max_iterations); + ClassDB::bind_method(D_METHOD("get_chain_max_iterations"), &SkeletonModification3DFABRIK::get_chain_max_iterations); + + // FABRIK joint data functions + ClassDB::bind_method(D_METHOD("get_fabrik_joint_bone_name", "joint_idx"), &SkeletonModification3DFABRIK::get_fabrik_joint_bone_name); + ClassDB::bind_method(D_METHOD("set_fabrik_joint_bone_name", "joint_idx", "bone_name"), &SkeletonModification3DFABRIK::set_fabrik_joint_bone_name); + ClassDB::bind_method(D_METHOD("get_fabrik_joint_bone_index", "joint_idx"), &SkeletonModification3DFABRIK::get_fabrik_joint_bone_index); + ClassDB::bind_method(D_METHOD("set_fabrik_joint_bone_index", "joint_idx", "bone_index"), &SkeletonModification3DFABRIK::set_fabrik_joint_bone_index); + ClassDB::bind_method(D_METHOD("get_fabrik_joint_length", "joint_idx"), &SkeletonModification3DFABRIK::get_fabrik_joint_length); + ClassDB::bind_method(D_METHOD("set_fabrik_joint_length", "joint_idx", "length"), &SkeletonModification3DFABRIK::set_fabrik_joint_length); + ClassDB::bind_method(D_METHOD("get_fabrik_joint_magnet", "joint_idx"), &SkeletonModification3DFABRIK::get_fabrik_joint_magnet); + ClassDB::bind_method(D_METHOD("set_fabrik_joint_magnet", "joint_idx", "magnet_position"), &SkeletonModification3DFABRIK::set_fabrik_joint_magnet); + ClassDB::bind_method(D_METHOD("get_fabrik_joint_auto_calculate_length", "joint_idx"), &SkeletonModification3DFABRIK::get_fabrik_joint_auto_calculate_length); + ClassDB::bind_method(D_METHOD("set_fabrik_joint_auto_calculate_length", "joint_idx", "auto_calculate_length"), &SkeletonModification3DFABRIK::set_fabrik_joint_auto_calculate_length); + ClassDB::bind_method(D_METHOD("fabrik_joint_auto_calculate_length", "joint_idx"), &SkeletonModification3DFABRIK::fabrik_joint_auto_calculate_length); + ClassDB::bind_method(D_METHOD("get_fabrik_joint_use_tip_node", "joint_idx"), &SkeletonModification3DFABRIK::get_fabrik_joint_use_tip_node); + ClassDB::bind_method(D_METHOD("set_fabrik_joint_use_tip_node", "joint_idx", "use_tip_node"), &SkeletonModification3DFABRIK::set_fabrik_joint_use_tip_node); + ClassDB::bind_method(D_METHOD("get_fabrik_joint_tip_node", "joint_idx"), &SkeletonModification3DFABRIK::get_fabrik_joint_tip_node); + ClassDB::bind_method(D_METHOD("set_fabrik_joint_tip_node", "joint_idx", "tip_node"), &SkeletonModification3DFABRIK::set_fabrik_joint_tip_node); + ClassDB::bind_method(D_METHOD("get_fabrik_joint_use_target_basis", "joint_idx"), &SkeletonModification3DFABRIK::get_fabrik_joint_use_target_basis); + ClassDB::bind_method(D_METHOD("set_fabrik_joint_use_target_basis", "joint_idx", "use_target_basis"), &SkeletonModification3DFABRIK::set_fabrik_joint_use_target_basis); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_target_node", "get_target_node"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "fabrik_data_chain_length", PROPERTY_HINT_RANGE, "0,100,1"), "set_fabrik_data_chain_length", "get_fabrik_data_chain_length"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "chain_tolerance", PROPERTY_HINT_RANGE, "0,100,0.001"), "set_chain_tolerance", "get_chain_tolerance"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "chain_max_iterations", PROPERTY_HINT_RANGE, "1,50,1"), "set_chain_max_iterations", "get_chain_max_iterations"); +} + +SkeletonModification3DFABRIK::SkeletonModification3DFABRIK() { + stack = nullptr; + is_setup = false; + enabled = true; +} + +SkeletonModification3DFABRIK::~SkeletonModification3DFABRIK() { +} diff --git a/scene/resources/skeleton_modification_3d_fabrik.h b/scene/resources/skeleton_modification_3d_fabrik.h new file mode 100644 index 0000000000..9b5da883d4 --- /dev/null +++ b/scene/resources/skeleton_modification_3d_fabrik.h @@ -0,0 +1,122 @@ +/*************************************************************************/ +/* skeleton_modification_3d_fabrik.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "core/templates/local_vector.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/skeleton_modification_3d.h" + +#ifndef SKELETONMODIFICATION3DFABRIK_H +#define SKELETONMODIFICATION3DFABRIK_H + +class SkeletonModification3DFABRIK : public SkeletonModification3D { + GDCLASS(SkeletonModification3DFABRIK, SkeletonModification3D); + +private: + struct FABRIK_Joint_Data { + String bone_name = ""; + int bone_idx = -1; + real_t length = -1; + Vector3 magnet_position = Vector3(0, 0, 0); + + bool auto_calculate_length = true; + bool use_tip_node = false; + NodePath tip_node = NodePath(); + ObjectID tip_node_cache; + + bool use_target_basis = false; + real_t roll = 0; + }; + + LocalVector<FABRIK_Joint_Data> fabrik_data_chain; + NodePath target_node; + ObjectID target_node_cache; + + real_t chain_tolerance = 0.01; + int chain_max_iterations = 10; + int chain_iterations = 0; + + void update_target_cache(); + void update_joint_tip_cache(int p_joint_idx); + + int final_joint_idx = 0; + Transform3D target_global_pose = Transform3D(); + Transform3D origin_global_pose = Transform3D(); + + void chain_backwards(); + void chain_forwards(); + void chain_apply(); + +protected: + static void _bind_methods(); + bool _get(const StringName &p_path, Variant &r_ret) const; + bool _set(const StringName &p_path, const Variant &p_value); + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + virtual void _execute(real_t p_delta) override; + virtual void _setup_modification(SkeletonModificationStack3D *p_stack) override; + + void set_target_node(const NodePath &p_target_node); + NodePath get_target_node() const; + + int get_fabrik_data_chain_length(); + void set_fabrik_data_chain_length(int p_new_length); + + real_t get_chain_tolerance(); + void set_chain_tolerance(real_t p_tolerance); + + int get_chain_max_iterations(); + void set_chain_max_iterations(int p_iterations); + + String get_fabrik_joint_bone_name(int p_joint_idx) const; + void set_fabrik_joint_bone_name(int p_joint_idx, String p_bone_name); + int get_fabrik_joint_bone_index(int p_joint_idx) const; + void set_fabrik_joint_bone_index(int p_joint_idx, int p_bone_idx); + real_t get_fabrik_joint_length(int p_joint_idx) const; + void set_fabrik_joint_length(int p_joint_idx, real_t p_bone_length); + Vector3 get_fabrik_joint_magnet(int p_joint_idx) const; + void set_fabrik_joint_magnet(int p_joint_idx, Vector3 p_magnet); + bool get_fabrik_joint_auto_calculate_length(int p_joint_idx) const; + void set_fabrik_joint_auto_calculate_length(int p_joint_idx, bool p_auto_calculate); + void fabrik_joint_auto_calculate_length(int p_joint_idx); + bool get_fabrik_joint_use_tip_node(int p_joint_idx) const; + void set_fabrik_joint_use_tip_node(int p_joint_idx, bool p_use_tip_node); + NodePath get_fabrik_joint_tip_node(int p_joint_idx) const; + void set_fabrik_joint_tip_node(int p_joint_idx, NodePath p_tip_node); + bool get_fabrik_joint_use_target_basis(int p_joint_idx) const; + void set_fabrik_joint_use_target_basis(int p_joint_idx, bool p_use_basis); + real_t get_fabrik_joint_roll(int p_joint_idx) const; + void set_fabrik_joint_roll(int p_joint_idx, real_t p_roll); + + SkeletonModification3DFABRIK(); + ~SkeletonModification3DFABRIK(); +}; + +#endif //SKELETONMODIFICATION3DFABRIK_H diff --git a/scene/resources/skeleton_modification_3d_jiggle.cpp b/scene/resources/skeleton_modification_3d_jiggle.cpp new file mode 100644 index 0000000000..1fb7dad2ad --- /dev/null +++ b/scene/resources/skeleton_modification_3d_jiggle.cpp @@ -0,0 +1,573 @@ +/*************************************************************************/ +/* skeleton_modification_3d_jiggle.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "scene/resources/skeleton_modification_3d_jiggle.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/skeleton_modification_3d.h" + +bool SkeletonModification3DJiggle::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.begins_with("joint_data/")) { + const int jiggle_size = jiggle_data_chain.size(); + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, jiggle_size, false); + + if (what == "bone_name") { + set_jiggle_joint_bone_name(which, p_value); + } else if (what == "bone_index") { + set_jiggle_joint_bone_index(which, p_value); + } else if (what == "override_defaults") { + set_jiggle_joint_override(which, p_value); + } else if (what == "stiffness") { + set_jiggle_joint_stiffness(which, p_value); + } else if (what == "mass") { + set_jiggle_joint_mass(which, p_value); + } else if (what == "damping") { + set_jiggle_joint_damping(which, p_value); + } else if (what == "use_gravity") { + set_jiggle_joint_use_gravity(which, p_value); + } else if (what == "gravity") { + set_jiggle_joint_gravity(which, p_value); + } else if (what == "roll") { + set_jiggle_joint_roll(which, Math::deg2rad(real_t(p_value))); + } + return true; + } else { + if (path == "use_colliders") { + set_use_colliders(p_value); + } else if (path == "collision_mask") { + set_collision_mask(p_value); + } + return true; + } + return true; +} + +bool SkeletonModification3DJiggle::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.begins_with("joint_data/")) { + const int jiggle_size = jiggle_data_chain.size(); + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, jiggle_size, false); + + if (what == "bone_name") { + r_ret = get_jiggle_joint_bone_name(which); + } else if (what == "bone_index") { + r_ret = get_jiggle_joint_bone_index(which); + } else if (what == "override_defaults") { + r_ret = get_jiggle_joint_override(which); + } else if (what == "stiffness") { + r_ret = get_jiggle_joint_stiffness(which); + } else if (what == "mass") { + r_ret = get_jiggle_joint_mass(which); + } else if (what == "damping") { + r_ret = get_jiggle_joint_damping(which); + } else if (what == "use_gravity") { + r_ret = get_jiggle_joint_use_gravity(which); + } else if (what == "gravity") { + r_ret = get_jiggle_joint_gravity(which); + } else if (what == "roll") { + r_ret = Math::rad2deg(get_jiggle_joint_roll(which)); + } + return true; + } else { + if (path == "use_colliders") { + r_ret = get_use_colliders(); + } else if (path == "collision_mask") { + r_ret = get_collision_mask(); + } + return true; + } + return true; +} + +void SkeletonModification3DJiggle::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back(PropertyInfo(Variant::BOOL, "use_colliders", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + if (use_colliders) { + p_list->push_back(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS, "", PROPERTY_USAGE_DEFAULT)); + } + + for (uint32_t i = 0; i < jiggle_data_chain.size(); i++) { + String base_string = "joint_data/" + itos(i) + "/"; + + p_list->push_back(PropertyInfo(Variant::STRING_NAME, base_string + "bone_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::INT, base_string + "bone_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "roll", PROPERTY_HINT_RANGE, "-360,360,0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "override_defaults", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + + if (jiggle_data_chain[i].override_defaults) { + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "stiffness", PROPERTY_HINT_RANGE, "0, 1000, 0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "mass", PROPERTY_HINT_RANGE, "0, 1000, 0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "damping", PROPERTY_HINT_RANGE, "0, 1, 0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "use_gravity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + if (jiggle_data_chain[i].use_gravity) { + p_list->push_back(PropertyInfo(Variant::VECTOR3, base_string + "gravity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } + } + } +} + +void SkeletonModification3DJiggle::_execute(real_t p_delta) { + ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot execute!"); + if (!enabled) { + return; + } + if (target_node_cache.is_null()) { + _print_execution_error(true, "Target cache is out of date. Attempting to update..."); + update_cache(); + return; + } + Node3D *target = Object::cast_to<Node3D>(ObjectDB::get_instance(target_node_cache)); + _print_execution_error(!target || !target->is_inside_tree(), "Target node is not in the scene tree. Cannot execute modification!"); + + for (uint32_t i = 0; i < jiggle_data_chain.size(); i++) { + _execute_jiggle_joint(i, target, p_delta); + } + + execution_error_found = false; +} + +void SkeletonModification3DJiggle::_execute_jiggle_joint(int p_joint_idx, Node3D *p_target, real_t p_delta) { + // Adopted from: https://wiki.unity3d.com/index.php/JiggleBone + // With modifications by TwistedTwigleg. + + if (jiggle_data_chain[p_joint_idx].bone_idx <= -2) { + jiggle_data_chain[p_joint_idx].bone_idx = stack->skeleton->find_bone(jiggle_data_chain[p_joint_idx].bone_name); + } + if (_print_execution_error( + jiggle_data_chain[p_joint_idx].bone_idx < 0 || jiggle_data_chain[p_joint_idx].bone_idx > stack->skeleton->get_bone_count(), + "Jiggle joint " + itos(p_joint_idx) + " bone index is invald. Cannot execute modification!")) { + return; + } + + Transform3D new_bone_trans = stack->skeleton->local_pose_to_global_pose(jiggle_data_chain[p_joint_idx].bone_idx, stack->skeleton->get_bone_local_pose_override(jiggle_data_chain[p_joint_idx].bone_idx)); + Vector3 target_position = stack->skeleton->world_transform_to_global_pose(p_target->get_global_transform()).origin; + + jiggle_data_chain[p_joint_idx].force = (target_position - jiggle_data_chain[p_joint_idx].dynamic_position) * jiggle_data_chain[p_joint_idx].stiffness * p_delta; + + if (jiggle_data_chain[p_joint_idx].use_gravity) { + Vector3 gravity_to_apply = new_bone_trans.basis.inverse().xform(jiggle_data_chain[p_joint_idx].gravity); + jiggle_data_chain[p_joint_idx].force += gravity_to_apply * p_delta; + } + + jiggle_data_chain[p_joint_idx].acceleration = jiggle_data_chain[p_joint_idx].force / jiggle_data_chain[p_joint_idx].mass; + jiggle_data_chain[p_joint_idx].velocity += jiggle_data_chain[p_joint_idx].acceleration * (1 - jiggle_data_chain[p_joint_idx].damping); + + jiggle_data_chain[p_joint_idx].dynamic_position += jiggle_data_chain[p_joint_idx].velocity + jiggle_data_chain[p_joint_idx].force; + jiggle_data_chain[p_joint_idx].dynamic_position += new_bone_trans.origin - jiggle_data_chain[p_joint_idx].last_position; + jiggle_data_chain[p_joint_idx].last_position = new_bone_trans.origin; + + // Collision detection/response + if (use_colliders) { + if (execution_mode == SkeletonModificationStack3D::EXECUTION_MODE::execution_mode_physics_process) { + Ref<World3D> world_3d = stack->skeleton->get_world_3d(); + ERR_FAIL_COND(world_3d.is_null()); + PhysicsDirectSpaceState3D *space_state = PhysicsServer3D::get_singleton()->space_get_direct_state(world_3d->get_space()); + PhysicsDirectSpaceState3D::RayResult ray_result; + + // Convert to world transforms, which is what the physics server needs + Transform3D new_bone_trans_world = stack->skeleton->global_pose_to_world_transform(new_bone_trans); + Transform3D dynamic_position_world = stack->skeleton->global_pose_to_world_transform(Transform3D(Basis(), jiggle_data_chain[p_joint_idx].dynamic_position)); + + bool ray_hit = space_state->intersect_ray(new_bone_trans_world.origin, dynamic_position_world.get_origin(), + ray_result, Set<RID>(), collision_mask); + + if (ray_hit) { + jiggle_data_chain[p_joint_idx].dynamic_position = jiggle_data_chain[p_joint_idx].last_noncollision_position; + jiggle_data_chain[p_joint_idx].acceleration = Vector3(0, 0, 0); + jiggle_data_chain[p_joint_idx].velocity = Vector3(0, 0, 0); + } else { + jiggle_data_chain[p_joint_idx].last_noncollision_position = jiggle_data_chain[p_joint_idx].dynamic_position; + } + + } else { + WARN_PRINT_ONCE("Jiggle modifier: You cannot detect colliders without the stack mode being set to _physics_process!"); + } + } + + // Get the forward direction that the basis is facing in right now. + stack->skeleton->update_bone_rest_forward_vector(jiggle_data_chain[p_joint_idx].bone_idx); + Vector3 forward_vector = stack->skeleton->get_bone_axis_forward_vector(jiggle_data_chain[p_joint_idx].bone_idx); + + // Rotate the bone using the dynamic position! + new_bone_trans.basis.rotate_to_align(forward_vector, new_bone_trans.origin.direction_to(jiggle_data_chain[p_joint_idx].dynamic_position)); + + // Roll + new_bone_trans.basis.rotate_local(forward_vector, jiggle_data_chain[p_joint_idx].roll); + + new_bone_trans = stack->skeleton->global_pose_to_local_pose(jiggle_data_chain[p_joint_idx].bone_idx, new_bone_trans); + stack->skeleton->set_bone_local_pose_override(jiggle_data_chain[p_joint_idx].bone_idx, new_bone_trans, stack->strength, true); + stack->skeleton->force_update_bone_children_transforms(jiggle_data_chain[p_joint_idx].bone_idx); +} + +void SkeletonModification3DJiggle::_update_jiggle_joint_data() { + for (uint32_t i = 0; i < jiggle_data_chain.size(); i++) { + if (!jiggle_data_chain[i].override_defaults) { + set_jiggle_joint_stiffness(i, stiffness); + set_jiggle_joint_mass(i, mass); + set_jiggle_joint_damping(i, damping); + set_jiggle_joint_use_gravity(i, use_gravity); + set_jiggle_joint_gravity(i, gravity); + } + } +} + +void SkeletonModification3DJiggle::_setup_modification(SkeletonModificationStack3D *p_stack) { + stack = p_stack; + + if (stack) { + is_setup = true; + execution_error_found = false; + + if (stack->skeleton) { + for (uint32_t i = 0; i < jiggle_data_chain.size(); i++) { + int bone_idx = jiggle_data_chain[i].bone_idx; + if (bone_idx > 0 && bone_idx < stack->skeleton->get_bone_count()) { + jiggle_data_chain[i].dynamic_position = stack->skeleton->local_pose_to_global_pose(bone_idx, stack->skeleton->get_bone_local_pose_override(bone_idx)).origin; + } + } + } + + update_cache(); + } +} + +void SkeletonModification3DJiggle::update_cache() { + if (!is_setup || !stack) { + _print_execution_error(true, "Cannot update target cache: modification is not properly setup!"); + return; + } + + target_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(target_node)) { + Node *node = stack->skeleton->get_node(target_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update target cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update target cache: node is not in the scene tree!"); + target_node_cache = node->get_instance_id(); + + execution_error_found = false; + } + } + } +} + +void SkeletonModification3DJiggle::set_target_node(const NodePath &p_target_node) { + target_node = p_target_node; + update_cache(); +} + +NodePath SkeletonModification3DJiggle::get_target_node() const { + return target_node; +} + +void SkeletonModification3DJiggle::set_stiffness(real_t p_stiffness) { + ERR_FAIL_COND_MSG(p_stiffness < 0, "Stiffness cannot be set to a negative value!"); + stiffness = p_stiffness; + _update_jiggle_joint_data(); +} + +real_t SkeletonModification3DJiggle::get_stiffness() const { + return stiffness; +} + +void SkeletonModification3DJiggle::set_mass(real_t p_mass) { + ERR_FAIL_COND_MSG(p_mass < 0, "Mass cannot be set to a negative value!"); + mass = p_mass; + _update_jiggle_joint_data(); +} + +real_t SkeletonModification3DJiggle::get_mass() const { + return mass; +} + +void SkeletonModification3DJiggle::set_damping(real_t p_damping) { + ERR_FAIL_COND_MSG(p_damping < 0, "Damping cannot be set to a negative value!"); + ERR_FAIL_COND_MSG(p_damping > 1, "Damping cannot be more than one!"); + damping = p_damping; + _update_jiggle_joint_data(); +} + +real_t SkeletonModification3DJiggle::get_damping() const { + return damping; +} + +void SkeletonModification3DJiggle::set_use_gravity(bool p_use_gravity) { + use_gravity = p_use_gravity; + _update_jiggle_joint_data(); +} + +bool SkeletonModification3DJiggle::get_use_gravity() const { + return use_gravity; +} + +void SkeletonModification3DJiggle::set_gravity(Vector3 p_gravity) { + gravity = p_gravity; + _update_jiggle_joint_data(); +} + +Vector3 SkeletonModification3DJiggle::get_gravity() const { + return gravity; +} + +void SkeletonModification3DJiggle::set_use_colliders(bool p_use_collider) { + use_colliders = p_use_collider; + notify_property_list_changed(); +} + +bool SkeletonModification3DJiggle::get_use_colliders() const { + return use_colliders; +} + +void SkeletonModification3DJiggle::set_collision_mask(int p_mask) { + collision_mask = p_mask; +} + +int SkeletonModification3DJiggle::get_collision_mask() const { + return collision_mask; +} + +// Jiggle joint data functions +int SkeletonModification3DJiggle::get_jiggle_data_chain_length() { + return jiggle_data_chain.size(); +} + +void SkeletonModification3DJiggle::set_jiggle_data_chain_length(int p_length) { + ERR_FAIL_COND(p_length < 0); + jiggle_data_chain.resize(p_length); + execution_error_found = false; + notify_property_list_changed(); +} + +void SkeletonModification3DJiggle::set_jiggle_joint_bone_name(int p_joint_idx, String p_name) { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + + jiggle_data_chain[p_joint_idx].bone_name = p_name; + if (stack && stack->skeleton) { + jiggle_data_chain[p_joint_idx].bone_idx = stack->skeleton->find_bone(p_name); + } + execution_error_found = false; + notify_property_list_changed(); +} + +String SkeletonModification3DJiggle::get_jiggle_joint_bone_name(int p_joint_idx) const { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, ""); + return jiggle_data_chain[p_joint_idx].bone_name; +} + +int SkeletonModification3DJiggle::get_jiggle_joint_bone_index(int p_joint_idx) const { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, -1); + return jiggle_data_chain[p_joint_idx].bone_idx; +} + +void SkeletonModification3DJiggle::set_jiggle_joint_bone_index(int p_joint_idx, int p_bone_idx) { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); + jiggle_data_chain[p_joint_idx].bone_idx = p_bone_idx; + + if (stack) { + if (stack->skeleton) { + jiggle_data_chain[p_joint_idx].bone_name = stack->skeleton->get_bone_name(p_bone_idx); + } + } + execution_error_found = false; + notify_property_list_changed(); +} + +void SkeletonModification3DJiggle::set_jiggle_joint_override(int p_joint_idx, bool p_override) { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + jiggle_data_chain[p_joint_idx].override_defaults = p_override; + _update_jiggle_joint_data(); + notify_property_list_changed(); +} + +bool SkeletonModification3DJiggle::get_jiggle_joint_override(int p_joint_idx) const { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); + return jiggle_data_chain[p_joint_idx].override_defaults; +} + +void SkeletonModification3DJiggle::set_jiggle_joint_stiffness(int p_joint_idx, real_t p_stiffness) { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_COND_MSG(p_stiffness < 0, "Stiffness cannot be set to a negative value!"); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + jiggle_data_chain[p_joint_idx].stiffness = p_stiffness; +} + +real_t SkeletonModification3DJiggle::get_jiggle_joint_stiffness(int p_joint_idx) const { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, -1); + return jiggle_data_chain[p_joint_idx].stiffness; +} + +void SkeletonModification3DJiggle::set_jiggle_joint_mass(int p_joint_idx, real_t p_mass) { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_COND_MSG(p_mass < 0, "Mass cannot be set to a negative value!"); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + jiggle_data_chain[p_joint_idx].mass = p_mass; +} + +real_t SkeletonModification3DJiggle::get_jiggle_joint_mass(int p_joint_idx) const { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, -1); + return jiggle_data_chain[p_joint_idx].mass; +} + +void SkeletonModification3DJiggle::set_jiggle_joint_damping(int p_joint_idx, real_t p_damping) { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_COND_MSG(p_damping < 0, "Damping cannot be set to a negative value!"); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + jiggle_data_chain[p_joint_idx].damping = p_damping; +} + +real_t SkeletonModification3DJiggle::get_jiggle_joint_damping(int p_joint_idx) const { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, -1); + return jiggle_data_chain[p_joint_idx].damping; +} + +void SkeletonModification3DJiggle::set_jiggle_joint_use_gravity(int p_joint_idx, bool p_use_gravity) { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + jiggle_data_chain[p_joint_idx].use_gravity = p_use_gravity; + notify_property_list_changed(); +} + +bool SkeletonModification3DJiggle::get_jiggle_joint_use_gravity(int p_joint_idx) const { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); + return jiggle_data_chain[p_joint_idx].use_gravity; +} + +void SkeletonModification3DJiggle::set_jiggle_joint_gravity(int p_joint_idx, Vector3 p_gravity) { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + jiggle_data_chain[p_joint_idx].gravity = p_gravity; +} + +Vector3 SkeletonModification3DJiggle::get_jiggle_joint_gravity(int p_joint_idx) const { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, Vector3(0, 0, 0)); + return jiggle_data_chain[p_joint_idx].gravity; +} + +void SkeletonModification3DJiggle::set_jiggle_joint_roll(int p_joint_idx, real_t p_roll) { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); + jiggle_data_chain[p_joint_idx].roll = p_roll; +} + +real_t SkeletonModification3DJiggle::get_jiggle_joint_roll(int p_joint_idx) const { + const int bone_chain_size = jiggle_data_chain.size(); + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, 0.0); + return jiggle_data_chain[p_joint_idx].roll; +} + +void SkeletonModification3DJiggle::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification3DJiggle::set_target_node); + ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification3DJiggle::get_target_node); + + ClassDB::bind_method(D_METHOD("set_jiggle_data_chain_length", "length"), &SkeletonModification3DJiggle::set_jiggle_data_chain_length); + ClassDB::bind_method(D_METHOD("get_jiggle_data_chain_length"), &SkeletonModification3DJiggle::get_jiggle_data_chain_length); + + ClassDB::bind_method(D_METHOD("set_stiffness", "stiffness"), &SkeletonModification3DJiggle::set_stiffness); + ClassDB::bind_method(D_METHOD("get_stiffness"), &SkeletonModification3DJiggle::get_stiffness); + ClassDB::bind_method(D_METHOD("set_mass", "mass"), &SkeletonModification3DJiggle::set_mass); + ClassDB::bind_method(D_METHOD("get_mass"), &SkeletonModification3DJiggle::get_mass); + ClassDB::bind_method(D_METHOD("set_damping", "damping"), &SkeletonModification3DJiggle::set_damping); + ClassDB::bind_method(D_METHOD("get_damping"), &SkeletonModification3DJiggle::get_damping); + ClassDB::bind_method(D_METHOD("set_use_gravity", "use_gravity"), &SkeletonModification3DJiggle::set_use_gravity); + ClassDB::bind_method(D_METHOD("get_use_gravity"), &SkeletonModification3DJiggle::get_use_gravity); + ClassDB::bind_method(D_METHOD("set_gravity", "gravity"), &SkeletonModification3DJiggle::set_gravity); + ClassDB::bind_method(D_METHOD("get_gravity"), &SkeletonModification3DJiggle::get_gravity); + + ClassDB::bind_method(D_METHOD("set_use_colliders", "use_colliders"), &SkeletonModification3DJiggle::set_use_colliders); + ClassDB::bind_method(D_METHOD("get_use_colliders"), &SkeletonModification3DJiggle::get_use_colliders); + ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &SkeletonModification3DJiggle::set_collision_mask); + ClassDB::bind_method(D_METHOD("get_collision_mask"), &SkeletonModification3DJiggle::get_collision_mask); + + // Jiggle joint data functions + ClassDB::bind_method(D_METHOD("set_jiggle_joint_bone_name", "joint_idx", "name"), &SkeletonModification3DJiggle::set_jiggle_joint_bone_name); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_bone_name", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_bone_name); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_bone_index", "joint_idx", "bone_idx"), &SkeletonModification3DJiggle::set_jiggle_joint_bone_index); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_bone_index", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_bone_index); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_override", "joint_idx", "override"), &SkeletonModification3DJiggle::set_jiggle_joint_override); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_override", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_override); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_stiffness", "joint_idx", "stiffness"), &SkeletonModification3DJiggle::set_jiggle_joint_stiffness); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_stiffness", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_stiffness); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_mass", "joint_idx", "mass"), &SkeletonModification3DJiggle::set_jiggle_joint_mass); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_mass", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_mass); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_damping", "joint_idx", "damping"), &SkeletonModification3DJiggle::set_jiggle_joint_damping); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_damping", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_damping); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_use_gravity", "joint_idx", "use_gravity"), &SkeletonModification3DJiggle::set_jiggle_joint_use_gravity); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_use_gravity", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_use_gravity); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_gravity", "joint_idx", "gravity"), &SkeletonModification3DJiggle::set_jiggle_joint_gravity); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_gravity", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_gravity); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_roll", "joint_idx", "roll"), &SkeletonModification3DJiggle::set_jiggle_joint_roll); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_roll", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_roll); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_target_node", "get_target_node"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "jiggle_data_chain_length", PROPERTY_HINT_RANGE, "0,100,1"), "set_jiggle_data_chain_length", "get_jiggle_data_chain_length"); + ADD_GROUP("Default Joint Settings", ""); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "stiffness"), "set_stiffness", "get_stiffness"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass"), "set_mass", "get_mass"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping", PROPERTY_HINT_RANGE, "0, 1, 0.01"), "set_damping", "get_damping"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_gravity"), "set_use_gravity", "get_use_gravity"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "gravity"), "set_gravity", "get_gravity"); + ADD_GROUP("", ""); +} + +SkeletonModification3DJiggle::SkeletonModification3DJiggle() { + stack = nullptr; + is_setup = false; + jiggle_data_chain = Vector<Jiggle_Joint_Data>(); + stiffness = 3; + mass = 0.75; + damping = 0.75; + use_gravity = false; + gravity = Vector3(0, -6.0, 0); + enabled = true; +} + +SkeletonModification3DJiggle::~SkeletonModification3DJiggle() { +} diff --git a/scene/resources/skeleton_modification_3d_jiggle.h b/scene/resources/skeleton_modification_3d_jiggle.h new file mode 100644 index 0000000000..c210c8fa73 --- /dev/null +++ b/scene/resources/skeleton_modification_3d_jiggle.h @@ -0,0 +1,138 @@ +/*************************************************************************/ +/* skeleton_modification_3d_jiggle.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "core/templates/local_vector.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/skeleton_modification_3d.h" + +#ifndef SKELETONMODIFICATION3DJIGGLE_H +#define SKELETONMODIFICATION3DJIGGLE_H + +class SkeletonModification3DJiggle : public SkeletonModification3D { + GDCLASS(SkeletonModification3DJiggle, SkeletonModification3D); + +private: + struct Jiggle_Joint_Data { + String bone_name = ""; + int bone_idx = -1; + + bool override_defaults = false; + real_t stiffness = 3; + real_t mass = 0.75; + real_t damping = 0.75; + bool use_gravity = false; + Vector3 gravity = Vector3(0, -6.0, 0); + real_t roll = 0; + + Vector3 cached_rotation = Vector3(0, 0, 0); + Vector3 force = Vector3(0, 0, 0); + Vector3 acceleration = Vector3(0, 0, 0); + Vector3 velocity = Vector3(0, 0, 0); + Vector3 last_position = Vector3(0, 0, 0); + Vector3 dynamic_position = Vector3(0, 0, 0); + + Vector3 last_noncollision_position = Vector3(0, 0, 0); + }; + + NodePath target_node; + ObjectID target_node_cache; + LocalVector<Jiggle_Joint_Data> jiggle_data_chain; + + real_t stiffness = 3; + real_t mass = 0.75; + real_t damping = 0.75; + bool use_gravity = false; + Vector3 gravity = Vector3(0, -6.0, 0); + + bool use_colliders = false; + uint32_t collision_mask = 1; + + void update_cache(); + void _execute_jiggle_joint(int p_joint_idx, Node3D *p_target, real_t p_delta); + void _update_jiggle_joint_data(); + +protected: + static void _bind_methods(); + bool _get(const StringName &p_path, Variant &r_ret) const; + bool _set(const StringName &p_path, const Variant &p_value); + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + virtual void _execute(real_t p_delta) override; + virtual void _setup_modification(SkeletonModificationStack3D *p_stack) override; + + void set_target_node(const NodePath &p_target_node); + NodePath get_target_node() const; + + void set_stiffness(real_t p_stiffness); + real_t get_stiffness() const; + void set_mass(real_t p_mass); + real_t get_mass() const; + void set_damping(real_t p_damping); + real_t get_damping() const; + + void set_use_gravity(bool p_use_gravity); + bool get_use_gravity() const; + void set_gravity(Vector3 p_gravity); + Vector3 get_gravity() const; + + void set_use_colliders(bool p_use_colliders); + bool get_use_colliders() const; + void set_collision_mask(int p_mask); + int get_collision_mask() const; + + int get_jiggle_data_chain_length(); + void set_jiggle_data_chain_length(int p_new_length); + + void set_jiggle_joint_bone_name(int p_joint_idx, String p_name); + String get_jiggle_joint_bone_name(int p_joint_idx) const; + void set_jiggle_joint_bone_index(int p_joint_idx, int p_idx); + int get_jiggle_joint_bone_index(int p_joint_idx) const; + + void set_jiggle_joint_override(int p_joint_idx, bool p_override); + bool get_jiggle_joint_override(int p_joint_idx) const; + void set_jiggle_joint_stiffness(int p_joint_idx, real_t p_stiffness); + real_t get_jiggle_joint_stiffness(int p_joint_idx) const; + void set_jiggle_joint_mass(int p_joint_idx, real_t p_mass); + real_t get_jiggle_joint_mass(int p_joint_idx) const; + void set_jiggle_joint_damping(int p_joint_idx, real_t p_damping); + real_t get_jiggle_joint_damping(int p_joint_idx) const; + void set_jiggle_joint_use_gravity(int p_joint_idx, bool p_use_gravity); + bool get_jiggle_joint_use_gravity(int p_joint_idx) const; + void set_jiggle_joint_gravity(int p_joint_idx, Vector3 p_gravity); + Vector3 get_jiggle_joint_gravity(int p_joint_idx) const; + void set_jiggle_joint_roll(int p_joint_idx, real_t p_roll); + real_t get_jiggle_joint_roll(int p_joint_idx) const; + + SkeletonModification3DJiggle(); + ~SkeletonModification3DJiggle(); +}; + +#endif //SKELETONMODIFICATION3DJIGGLE_H diff --git a/scene/resources/skeleton_modification_3d_lookat.cpp b/scene/resources/skeleton_modification_3d_lookat.cpp new file mode 100644 index 0000000000..afdb077e71 --- /dev/null +++ b/scene/resources/skeleton_modification_3d_lookat.cpp @@ -0,0 +1,265 @@ +/*************************************************************************/ +/* skeleton_modification_3d_lookat.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "scene/resources/skeleton_modification_3d_lookat.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/skeleton_modification_3d.h" + +bool SkeletonModification3DLookAt::_set(const StringName &p_path, const Variant &p_value) { + if (p_path == "lock_rotation_to_plane") { + set_lock_rotation_to_plane(p_value); + } else if (p_path == "lock_rotation_plane") { + set_lock_rotation_plane(p_value); + } else if (p_path == "additional_rotation") { + Vector3 tmp = p_value; + tmp.x = Math::deg2rad(tmp.x); + tmp.y = Math::deg2rad(tmp.y); + tmp.z = Math::deg2rad(tmp.z); + set_additional_rotation(tmp); + } + + return true; +} + +bool SkeletonModification3DLookAt::_get(const StringName &p_path, Variant &r_ret) const { + if (p_path == "lock_rotation_to_plane") { + r_ret = get_lock_rotation_to_plane(); + } else if (p_path == "lock_rotation_plane") { + r_ret = get_lock_rotation_plane(); + } else if (p_path == "additional_rotation") { + Vector3 tmp = get_additional_rotation(); + tmp.x = Math::rad2deg(tmp.x); + tmp.y = Math::rad2deg(tmp.y); + tmp.z = Math::rad2deg(tmp.z); + r_ret = tmp; + } + + return true; +} + +void SkeletonModification3DLookAt::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back(PropertyInfo(Variant::BOOL, "lock_rotation_to_plane", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + if (lock_rotation_to_plane) { + p_list->push_back(PropertyInfo(Variant::INT, "lock_rotation_plane", PROPERTY_HINT_ENUM, "X plane, Y plane, Z plane", PROPERTY_USAGE_DEFAULT)); + } + p_list->push_back(PropertyInfo(Variant::VECTOR3, "additional_rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); +} + +void SkeletonModification3DLookAt::_execute(real_t p_delta) { + ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot execute!"); + if (!enabled) { + return; + } + + if (target_node_cache.is_null()) { + _print_execution_error(true, "Target cache is out of date. Attempting to update..."); + update_cache(); + return; + } + + if (bone_idx <= -2) { + bone_idx = stack->skeleton->find_bone(bone_name); + } + + Node3D *target = Object::cast_to<Node3D>(ObjectDB::get_instance(target_node_cache)); + if (_print_execution_error(!target || !target->is_inside_tree(), "Target node is not in the scene tree. Cannot execute modification!")) { + return; + } + if (_print_execution_error(bone_idx <= -1, "Bone index is invalid. Cannot execute modification!")) { + return; + } + + Transform3D new_bone_trans = stack->skeleton->get_bone_local_pose_override(bone_idx); + Vector3 target_pos = stack->skeleton->global_pose_to_local_pose(bone_idx, stack->skeleton->world_transform_to_global_pose(target->get_global_transform())).origin; + + // Lock the rotation to a plane relative to the bone by changing the target position + if (lock_rotation_to_plane) { + if (lock_rotation_plane == ROTATION_PLANE::ROTATION_PLANE_X) { + target_pos.x = new_bone_trans.origin.x; + } else if (lock_rotation_plane == ROTATION_PLANE::ROTATION_PLANE_Y) { + target_pos.y = new_bone_trans.origin.y; + } else if (lock_rotation_plane == ROTATION_PLANE::ROTATION_PLANE_Z) { + target_pos.z = new_bone_trans.origin.z; + } + } + + // Look at the target! + new_bone_trans = new_bone_trans.looking_at(target_pos, Vector3(0, 1, 0)); + // Convert from Z-forward to whatever direction the bone faces. + stack->skeleton->update_bone_rest_forward_vector(bone_idx); + new_bone_trans.basis = stack->skeleton->global_pose_z_forward_to_bone_forward(bone_idx, new_bone_trans.basis); + + // Apply additional rotation + new_bone_trans.basis.rotate_local(Vector3(1, 0, 0), additional_rotation.x); + new_bone_trans.basis.rotate_local(Vector3(0, 1, 0), additional_rotation.y); + new_bone_trans.basis.rotate_local(Vector3(0, 0, 1), additional_rotation.z); + + stack->skeleton->set_bone_local_pose_override(bone_idx, new_bone_trans, stack->strength, true); + stack->skeleton->force_update_bone_children_transforms(bone_idx); + + // If we completed it successfully, then we can set execution_error_found to false + execution_error_found = false; +} + +void SkeletonModification3DLookAt::_setup_modification(SkeletonModificationStack3D *p_stack) { + stack = p_stack; + + if (stack != nullptr) { + is_setup = true; + execution_error_found = false; + update_cache(); + } +} + +void SkeletonModification3DLookAt::set_bone_name(String p_name) { + bone_name = p_name; + if (stack) { + if (stack->skeleton) { + bone_idx = stack->skeleton->find_bone(bone_name); + } + } + execution_error_found = false; + notify_property_list_changed(); +} + +String SkeletonModification3DLookAt::get_bone_name() const { + return bone_name; +} + +int SkeletonModification3DLookAt::get_bone_index() const { + return bone_idx; +} + +void SkeletonModification3DLookAt::set_bone_index(int p_bone_idx) { + ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); + bone_idx = p_bone_idx; + + if (stack) { + if (stack->skeleton) { + bone_name = stack->skeleton->get_bone_name(p_bone_idx); + } + } + execution_error_found = false; + notify_property_list_changed(); +} + +void SkeletonModification3DLookAt::update_cache() { + if (!is_setup || !stack) { + _print_execution_error(true, "Cannot update target cache: modification is not properly setup!"); + return; + } + + target_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(target_node)) { + Node *node = stack->skeleton->get_node(target_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update target cache: Node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update target cache: Node is not in the scene tree!"); + target_node_cache = node->get_instance_id(); + + execution_error_found = false; + } + } + } +} + +void SkeletonModification3DLookAt::set_target_node(const NodePath &p_target_node) { + target_node = p_target_node; + update_cache(); +} + +NodePath SkeletonModification3DLookAt::get_target_node() const { + return target_node; +} + +Vector3 SkeletonModification3DLookAt::get_additional_rotation() const { + return additional_rotation; +} + +void SkeletonModification3DLookAt::set_additional_rotation(Vector3 p_offset) { + additional_rotation = p_offset; +} + +bool SkeletonModification3DLookAt::get_lock_rotation_to_plane() const { + return lock_rotation_plane; +} + +void SkeletonModification3DLookAt::set_lock_rotation_to_plane(bool p_lock_rotation) { + lock_rotation_to_plane = p_lock_rotation; + notify_property_list_changed(); +} + +int SkeletonModification3DLookAt::get_lock_rotation_plane() const { + return lock_rotation_plane; +} + +void SkeletonModification3DLookAt::set_lock_rotation_plane(int p_plane) { + lock_rotation_plane = p_plane; +} + +void SkeletonModification3DLookAt::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_bone_name", "name"), &SkeletonModification3DLookAt::set_bone_name); + ClassDB::bind_method(D_METHOD("get_bone_name"), &SkeletonModification3DLookAt::get_bone_name); + + ClassDB::bind_method(D_METHOD("set_bone_index", "bone_idx"), &SkeletonModification3DLookAt::set_bone_index); + ClassDB::bind_method(D_METHOD("get_bone_index"), &SkeletonModification3DLookAt::get_bone_index); + + ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification3DLookAt::set_target_node); + ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification3DLookAt::get_target_node); + + ClassDB::bind_method(D_METHOD("set_additional_rotation", "additional_rotation"), &SkeletonModification3DLookAt::set_additional_rotation); + ClassDB::bind_method(D_METHOD("get_additional_rotation"), &SkeletonModification3DLookAt::get_additional_rotation); + + ClassDB::bind_method(D_METHOD("set_lock_rotation_to_plane", "lock_to_plane"), &SkeletonModification3DLookAt::set_lock_rotation_to_plane); + ClassDB::bind_method(D_METHOD("get_lock_rotation_to_plane"), &SkeletonModification3DLookAt::get_lock_rotation_to_plane); + ClassDB::bind_method(D_METHOD("set_lock_rotation_plane", "plane"), &SkeletonModification3DLookAt::set_lock_rotation_plane); + ClassDB::bind_method(D_METHOD("get_lock_rotation_plane"), &SkeletonModification3DLookAt::get_lock_rotation_plane); + + ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bone_name"), "set_bone_name", "get_bone_name"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_index"), "set_bone_index", "get_bone_index"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_target_node", "get_target_node"); +} + +SkeletonModification3DLookAt::SkeletonModification3DLookAt() { + stack = nullptr; + is_setup = false; + bone_name = ""; + bone_idx = -2; + additional_rotation = Vector3(); + lock_rotation_to_plane = false; + enabled = true; +} + +SkeletonModification3DLookAt::~SkeletonModification3DLookAt() { +} diff --git a/scene/resources/skeleton_modification_3d_lookat.h b/scene/resources/skeleton_modification_3d_lookat.h new file mode 100644 index 0000000000..5971e3f647 --- /dev/null +++ b/scene/resources/skeleton_modification_3d_lookat.h @@ -0,0 +1,89 @@ +/*************************************************************************/ +/* skeleton_modification_3d_lookat.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/skeleton_modification_3d.h" + +#ifndef SKELETONMODIFICATION3DLOOKAT_H +#define SKELETONMODIFICATION3DLOOKAT_H + +class SkeletonModification3DLookAt : public SkeletonModification3D { + GDCLASS(SkeletonModification3DLookAt, SkeletonModification3D); + +private: + String bone_name = ""; + int bone_idx = -1; + NodePath target_node; + ObjectID target_node_cache; + + Vector3 additional_rotation = Vector3(1, 0, 0); + bool lock_rotation_to_plane = false; + int lock_rotation_plane = ROTATION_PLANE_X; + + void update_cache(); + +protected: + static void _bind_methods(); + bool _get(const StringName &p_path, Variant &r_ret) const; + bool _set(const StringName &p_path, const Variant &p_value); + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + enum ROTATION_PLANE { + ROTATION_PLANE_X, + ROTATION_PLANE_Y, + ROTATION_PLANE_Z + }; + + virtual void _execute(real_t p_delta) override; + virtual void _setup_modification(SkeletonModificationStack3D *p_stack) override; + + void set_bone_name(String p_name); + String get_bone_name() const; + + void set_bone_index(int p_idx); + int get_bone_index() const; + + void set_target_node(const NodePath &p_target_node); + NodePath get_target_node() const; + + void set_additional_rotation(Vector3 p_offset); + Vector3 get_additional_rotation() const; + + void set_lock_rotation_to_plane(bool p_lock_to_plane); + bool get_lock_rotation_to_plane() const; + void set_lock_rotation_plane(int p_plane); + int get_lock_rotation_plane() const; + + SkeletonModification3DLookAt(); + ~SkeletonModification3DLookAt(); +}; + +#endif //SKELETONMODIFICATION3DLOOKAT_H diff --git a/scene/resources/skeleton_modification_3d_stackholder.cpp b/scene/resources/skeleton_modification_3d_stackholder.cpp new file mode 100644 index 0000000000..56035a4def --- /dev/null +++ b/scene/resources/skeleton_modification_3d_stackholder.cpp @@ -0,0 +1,104 @@ +/*************************************************************************/ +/* skeleton_modification_3d_stackholder.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "scene/resources/skeleton_modification_3d_stackholder.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/skeleton_modification_3d.h" + +bool SkeletonModification3DStackHolder::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path == "held_modification_stack") { + set_held_modification_stack(p_value); + } + return true; +} + +bool SkeletonModification3DStackHolder::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path == "held_modification_stack") { + r_ret = get_held_modification_stack(); + } + return true; +} + +void SkeletonModification3DStackHolder::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back(PropertyInfo(Variant::OBJECT, "held_modification_stack", PROPERTY_HINT_RESOURCE_TYPE, "SkeletonModificationStack3D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE)); +} + +void SkeletonModification3DStackHolder::_execute(real_t p_delta) { + ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot execute!"); + + if (held_modification_stack.is_valid()) { + held_modification_stack->execute(p_delta, execution_mode); + } +} + +void SkeletonModification3DStackHolder::_setup_modification(SkeletonModificationStack3D *p_stack) { + stack = p_stack; + + if (stack != nullptr) { + is_setup = true; + + if (held_modification_stack.is_valid()) { + held_modification_stack->set_skeleton(stack->get_skeleton()); + held_modification_stack->setup(); + } + } +} + +void SkeletonModification3DStackHolder::set_held_modification_stack(Ref<SkeletonModificationStack3D> p_held_stack) { + held_modification_stack = p_held_stack; + + if (is_setup && held_modification_stack.is_valid()) { + held_modification_stack->set_skeleton(stack->get_skeleton()); + held_modification_stack->setup(); + } +} + +Ref<SkeletonModificationStack3D> SkeletonModification3DStackHolder::get_held_modification_stack() const { + return held_modification_stack; +} + +void SkeletonModification3DStackHolder::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_held_modification_stack", "held_modification_stack"), &SkeletonModification3DStackHolder::set_held_modification_stack); + ClassDB::bind_method(D_METHOD("get_held_modification_stack"), &SkeletonModification3DStackHolder::get_held_modification_stack); +} + +SkeletonModification3DStackHolder::SkeletonModification3DStackHolder() { + stack = nullptr; + is_setup = false; + enabled = true; +} + +SkeletonModification3DStackHolder::~SkeletonModification3DStackHolder() { +} diff --git a/scene/resources/skeleton_modification_3d_stackholder.h b/scene/resources/skeleton_modification_3d_stackholder.h new file mode 100644 index 0000000000..c765cd8de3 --- /dev/null +++ b/scene/resources/skeleton_modification_3d_stackholder.h @@ -0,0 +1,59 @@ +/*************************************************************************/ +/* skeleton_modification_3d_stackholder.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/skeleton_modification_3d.h" + +#ifndef SKELETONMODIFICATION3DSTACKHOLDER_H +#define SKELETONMODIFICATION3DSTACKHOLDER_H + +class SkeletonModification3DStackHolder : public SkeletonModification3D { + GDCLASS(SkeletonModification3DStackHolder, SkeletonModification3D); + +protected: + static void _bind_methods(); + bool _get(const StringName &p_path, Variant &r_ret) const; + bool _set(const StringName &p_path, const Variant &p_value); + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + Ref<SkeletonModificationStack3D> held_modification_stack; + + virtual void _execute(real_t p_delta) override; + virtual void _setup_modification(SkeletonModificationStack3D *p_stack) override; + + void set_held_modification_stack(Ref<SkeletonModificationStack3D> p_held_stack); + Ref<SkeletonModificationStack3D> get_held_modification_stack() const; + + SkeletonModification3DStackHolder(); + ~SkeletonModification3DStackHolder(); +}; + +#endif //SKELETONMODIFICATION3DSTACKHOLDER_H diff --git a/scene/resources/skeleton_modification_3d_twoboneik.cpp b/scene/resources/skeleton_modification_3d_twoboneik.cpp new file mode 100644 index 0000000000..ae7a3bab7e --- /dev/null +++ b/scene/resources/skeleton_modification_3d_twoboneik.cpp @@ -0,0 +1,599 @@ +/*************************************************************************/ +/* skeleton_modification_3d_twoboneik.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "scene/resources/skeleton_modification_3d_twoboneik.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/skeleton_modification_3d.h" + +bool SkeletonModification3DTwoBoneIK::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path == "use_tip_node") { + set_use_tip_node(p_value); + } else if (path == "tip_node") { + set_tip_node(p_value); + } else if (path == "auto_calculate_joint_length") { + set_auto_calculate_joint_length(p_value); + } else if (path == "use_pole_node") { + set_use_pole_node(p_value); + } else if (path == "pole_node") { + set_pole_node(p_value); + } else if (path == "joint_one_length") { + set_joint_one_length(p_value); + } else if (path == "joint_two_length") { + set_joint_two_length(p_value); + } else if (path == "joint_one/bone_name") { + set_joint_one_bone_name(p_value); + } else if (path == "joint_one/bone_idx") { + set_joint_one_bone_idx(p_value); + } else if (path == "joint_one/roll") { + set_joint_one_roll(Math::deg2rad(real_t(p_value))); + } else if (path == "joint_two/bone_name") { + set_joint_two_bone_name(p_value); + } else if (path == "joint_two/bone_idx") { + set_joint_two_bone_idx(p_value); + } else if (path == "joint_two/roll") { + set_joint_two_roll(Math::deg2rad(real_t(p_value))); + } + + return true; +} + +bool SkeletonModification3DTwoBoneIK::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path == "use_tip_node") { + r_ret = get_use_tip_node(); + } else if (path == "tip_node") { + r_ret = get_tip_node(); + } else if (path == "auto_calculate_joint_length") { + r_ret = get_auto_calculate_joint_length(); + } else if (path == "use_pole_node") { + r_ret = get_use_pole_node(); + } else if (path == "pole_node") { + r_ret = get_pole_node(); + } else if (path == "joint_one_length") { + r_ret = get_joint_one_length(); + } else if (path == "joint_two_length") { + r_ret = get_joint_two_length(); + } else if (path == "joint_one/bone_name") { + r_ret = get_joint_one_bone_name(); + } else if (path == "joint_one/bone_idx") { + r_ret = get_joint_one_bone_idx(); + } else if (path == "joint_one/roll") { + r_ret = Math::rad2deg(get_joint_one_roll()); + } else if (path == "joint_two/bone_name") { + r_ret = get_joint_two_bone_name(); + } else if (path == "joint_two/bone_idx") { + r_ret = get_joint_two_bone_idx(); + } else if (path == "joint_two/roll") { + r_ret = Math::rad2deg(get_joint_two_roll()); + } + + return true; +} + +void SkeletonModification3DTwoBoneIK::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back(PropertyInfo(Variant::BOOL, "use_tip_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + if (use_tip_node) { + p_list->push_back(PropertyInfo(Variant::NODE_PATH, "tip_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D", PROPERTY_USAGE_DEFAULT)); + } + + p_list->push_back(PropertyInfo(Variant::BOOL, "auto_calculate_joint_length", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + if (!auto_calculate_joint_length) { + p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_one_length", PROPERTY_HINT_RANGE, "-1, 10000, 0.001", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_two_length", PROPERTY_HINT_RANGE, "-1, 10000, 0.001", PROPERTY_USAGE_DEFAULT)); + } + + p_list->push_back(PropertyInfo(Variant::BOOL, "use_pole_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + if (use_pole_node) { + p_list->push_back(PropertyInfo(Variant::NODE_PATH, "pole_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D", PROPERTY_USAGE_DEFAULT)); + } + + p_list->push_back(PropertyInfo(Variant::STRING_NAME, "joint_one/bone_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::INT, "joint_one/bone_idx", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_one/roll", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT)); + + p_list->push_back(PropertyInfo(Variant::STRING_NAME, "joint_two/bone_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::INT, "joint_two/bone_idx", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_two/roll", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT)); +} + +void SkeletonModification3DTwoBoneIK::_execute(real_t p_delta) { + ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot execute!"); + + if (!enabled) { + return; + } + + if (_print_execution_error(joint_one_bone_idx < 0 || joint_two_bone_idx < 0, + "One (or more) of the bones in the modification have invalid bone indexes. Cannot execute modification!")) { + return; + } + + if (target_node_cache.is_null()) { + _print_execution_error(true, "Target cache is out of date. Attempting to update..."); + update_cache_target(); + return; + } + + // Update joint lengths (if needed) + if (auto_calculate_joint_length && (joint_one_length < 0 || joint_two_length < 0)) { + calculate_joint_lengths(); + } + + // Adopted from the links below: + // http://theorangeduck.com/page/simple-two-joint + // https://www.alanzucconi.com/2018/05/02/ik-2d-2/ + // With modifications by TwistedTwigleg + Node3D *target = Object::cast_to<Node3D>(ObjectDB::get_instance(target_node_cache)); + if (_print_execution_error(!target || !target->is_inside_tree(), "Target node is not in the scene tree. Cannot execute modification!")) { + return; + } + Transform3D target_trans = stack->skeleton->world_transform_to_global_pose(target->get_global_transform()); + + Transform3D bone_one_trans; + Transform3D bone_two_trans; + + // Make the first joint look at the pole, and the second look at the target. That way, the + // TwoBoneIK solver has to really only handle extension/contraction, which should make it align with the pole. + if (use_pole_node) { + if (pole_node_cache.is_null()) { + _print_execution_error(true, "Pole cache is out of date. Attempting to update..."); + update_cache_pole(); + return; + } + + Node3D *pole = Object::cast_to<Node3D>(ObjectDB::get_instance(pole_node_cache)); + if (_print_execution_error(!pole || !pole->is_inside_tree(), "Pole node is not in the scene tree. Cannot execute modification!")) { + return; + } + Transform3D pole_trans = stack->skeleton->world_transform_to_global_pose(pole->get_global_transform()); + + bone_one_trans = stack->skeleton->local_pose_to_global_pose(joint_one_bone_idx, stack->skeleton->get_bone_local_pose_override(joint_one_bone_idx)); + bone_one_trans = bone_one_trans.looking_at(pole_trans.origin, Vector3(0, 1, 0)); + bone_one_trans.basis = stack->skeleton->global_pose_z_forward_to_bone_forward(joint_one_bone_idx, bone_one_trans.basis); + stack->skeleton->update_bone_rest_forward_vector(joint_one_bone_idx); + bone_one_trans.basis.rotate_local(stack->skeleton->get_bone_axis_forward_vector(joint_one_bone_idx), joint_one_roll); + stack->skeleton->set_bone_local_pose_override(joint_one_bone_idx, stack->skeleton->global_pose_to_local_pose(joint_one_bone_idx, bone_one_trans), stack->strength, true); + stack->skeleton->force_update_bone_children_transforms(joint_one_bone_idx); + + bone_two_trans = stack->skeleton->local_pose_to_global_pose(joint_two_bone_idx, stack->skeleton->get_bone_local_pose_override(joint_two_bone_idx)); + bone_two_trans = bone_two_trans.looking_at(target_trans.origin, Vector3(0, 1, 0)); + bone_two_trans.basis = stack->skeleton->global_pose_z_forward_to_bone_forward(joint_two_bone_idx, bone_two_trans.basis); + stack->skeleton->update_bone_rest_forward_vector(joint_two_bone_idx); + bone_two_trans.basis.rotate_local(stack->skeleton->get_bone_axis_forward_vector(joint_two_bone_idx), joint_two_roll); + stack->skeleton->set_bone_local_pose_override(joint_two_bone_idx, stack->skeleton->global_pose_to_local_pose(joint_two_bone_idx, bone_two_trans), stack->strength, true); + stack->skeleton->force_update_bone_children_transforms(joint_two_bone_idx); + } else { + bone_one_trans = stack->skeleton->local_pose_to_global_pose(joint_one_bone_idx, stack->skeleton->get_bone_local_pose_override(joint_one_bone_idx)); + bone_two_trans = stack->skeleton->local_pose_to_global_pose(joint_two_bone_idx, stack->skeleton->get_bone_local_pose_override(joint_two_bone_idx)); + } + + Transform3D bone_two_tip_trans; + if (use_tip_node) { + if (tip_node_cache.is_null()) { + _print_execution_error(true, "Tip cache is out of date. Attempting to update..."); + update_cache_tip(); + return; + } + Node3D *tip = Object::cast_to<Node3D>(ObjectDB::get_instance(tip_node_cache)); + if (_print_execution_error(!tip || !tip->is_inside_tree(), "Tip node is not in the scene tree. Cannot execute modification!")) { + return; + } + bone_two_tip_trans = stack->skeleton->world_transform_to_global_pose(tip->get_global_transform()); + } else { + stack->skeleton->update_bone_rest_forward_vector(joint_two_bone_idx); + bone_two_tip_trans = bone_two_trans; + bone_two_tip_trans.origin += bone_two_trans.basis.xform(stack->skeleton->get_bone_axis_forward_vector(joint_two_bone_idx)).normalized() * joint_two_length; + } + + real_t joint_one_to_target_length = bone_one_trans.origin.distance_to(target_trans.origin); + if (joint_one_length + joint_two_length < joint_one_to_target_length) { + // Set the target *just* out of reach to straighten the bones + joint_one_to_target_length = joint_one_length + joint_two_length + 0.01; + } else if (joint_one_to_target_length < joint_one_length) { + // Place the target in reach so the solver doesn't do crazy things + joint_one_to_target_length = joint_one_length; + } + + // Get the square lengths for all three sides of the triangle we'll use to calculate the angles + real_t sqr_one_length = joint_one_length * joint_one_length; + real_t sqr_two_length = joint_two_length * joint_two_length; + real_t sqr_three_length = joint_one_to_target_length * joint_one_to_target_length; + + // Calculate the angles for the first joint using the law of cosigns + real_t ac_ab_0 = Math::acos(CLAMP(bone_two_tip_trans.origin.direction_to(bone_one_trans.origin).dot(bone_two_trans.origin.direction_to(bone_one_trans.origin)), -1, 1)); + real_t ac_at_0 = Math::acos(CLAMP(bone_one_trans.origin.direction_to(bone_two_tip_trans.origin).dot(bone_one_trans.origin.direction_to(target_trans.origin)), -1, 1)); + real_t ac_ab_1 = Math::acos(CLAMP((sqr_two_length - sqr_one_length - sqr_three_length) / (-2.0 * joint_one_length * joint_one_to_target_length), -1, 1)); + + // Calculate the angles of rotation. Angle 0 is the extension/contraction axis, while angle 1 is the rotation axis to align the triangle to the target + Vector3 axis_0 = bone_one_trans.origin.direction_to(bone_two_tip_trans.origin).cross(bone_one_trans.origin.direction_to(bone_two_trans.origin)); + Vector3 axis_1 = bone_one_trans.origin.direction_to(bone_two_tip_trans.origin).cross(bone_one_trans.origin.direction_to(target_trans.origin)); + + // Make a quaternion with the delta rotation needed to rotate the first joint into alignment and apply it to the transform. + Quaternion bone_one_quat = bone_one_trans.basis.get_rotation_quaternion(); + Quaternion rot_0 = Quaternion(bone_one_quat.inverse().xform(axis_0).normalized(), (ac_ab_1 - ac_ab_0)); + Quaternion rot_2 = Quaternion(bone_one_quat.inverse().xform(axis_1).normalized(), ac_at_0); + bone_one_trans.basis.set_quaternion(bone_one_quat * (rot_0 * rot_2)); + + stack->skeleton->update_bone_rest_forward_vector(joint_one_bone_idx); + bone_one_trans.basis.rotate_local(stack->skeleton->get_bone_axis_forward_vector(joint_one_bone_idx), joint_one_roll); + + // Apply the rotation to the first joint + bone_one_trans = stack->skeleton->global_pose_to_local_pose(joint_one_bone_idx, bone_one_trans); + bone_one_trans.origin = Vector3(0, 0, 0); + stack->skeleton->set_bone_local_pose_override(joint_one_bone_idx, bone_one_trans, stack->strength, true); + stack->skeleton->force_update_bone_children_transforms(joint_one_bone_idx); + + if (use_pole_node) { + // Update bone_two_trans so its at the latest position, with the rotation of bone_one_trans taken into account, then look at the target. + bone_two_trans = stack->skeleton->local_pose_to_global_pose(joint_two_bone_idx, stack->skeleton->get_bone_local_pose_override(joint_two_bone_idx)); + stack->skeleton->update_bone_rest_forward_vector(joint_two_bone_idx); + Vector3 forward_vector = stack->skeleton->get_bone_axis_forward_vector(joint_two_bone_idx); + bone_two_trans.basis.rotate_to_align(forward_vector, bone_two_trans.origin.direction_to(target_trans.origin)); + + stack->skeleton->update_bone_rest_forward_vector(joint_two_bone_idx); + bone_two_trans.basis.rotate_local(stack->skeleton->get_bone_axis_forward_vector(joint_two_bone_idx), joint_two_roll); + + bone_two_trans = stack->skeleton->global_pose_to_local_pose(joint_two_bone_idx, bone_two_trans); + stack->skeleton->set_bone_local_pose_override(joint_two_bone_idx, bone_two_trans, stack->strength, true); + stack->skeleton->force_update_bone_children_transforms(joint_two_bone_idx); + } else { + // Calculate the angles for the second joint using the law of cosigns, make a quaternion with the delta rotation needed to rotate the joint into + // alignment, and then apply it to the second joint. + real_t ba_bc_0 = Math::acos(CLAMP(bone_two_trans.origin.direction_to(bone_one_trans.origin).dot(bone_two_trans.origin.direction_to(bone_two_tip_trans.origin)), -1, 1)); + real_t ba_bc_1 = Math::acos(CLAMP((sqr_three_length - sqr_one_length - sqr_two_length) / (-2.0 * joint_one_length * joint_two_length), -1, 1)); + Quaternion bone_two_quat = bone_two_trans.basis.get_rotation_quaternion(); + Quaternion rot_1 = Quaternion(bone_two_quat.inverse().xform(axis_0).normalized(), (ba_bc_1 - ba_bc_0)); + bone_two_trans.basis.set_quaternion(bone_two_quat * rot_1); + + stack->skeleton->update_bone_rest_forward_vector(joint_two_bone_idx); + bone_two_trans.basis.rotate_local(stack->skeleton->get_bone_axis_forward_vector(joint_two_bone_idx), joint_two_roll); + + bone_two_trans = stack->skeleton->global_pose_to_local_pose(joint_two_bone_idx, bone_two_trans); + bone_two_trans.origin = Vector3(0, 0, 0); + stack->skeleton->set_bone_local_pose_override(joint_two_bone_idx, bone_two_trans, stack->strength, true); + stack->skeleton->force_update_bone_children_transforms(joint_two_bone_idx); + } +} + +void SkeletonModification3DTwoBoneIK::_setup_modification(SkeletonModificationStack3D *p_stack) { + stack = p_stack; + + if (stack != nullptr) { + is_setup = true; + execution_error_found = false; + update_cache_target(); + update_cache_tip(); + } +} + +void SkeletonModification3DTwoBoneIK::update_cache_target() { + if (!is_setup || !stack) { + _print_execution_error(true, "Cannot update target cache: modification is not properly setup!"); + return; + } + + target_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree() && target_node.is_empty() == false) { + if (stack->skeleton->has_node(target_node)) { + Node *node = stack->skeleton->get_node(target_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update target cache: Target node is this modification's skeleton or cannot be found. Cannot execute modification"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update target cache: Target node is not in the scene tree. Cannot execute modification!"); + target_node_cache = node->get_instance_id(); + + execution_error_found = false; + } + } + } +} + +void SkeletonModification3DTwoBoneIK::update_cache_tip() { + if (!is_setup || !stack) { + _print_execution_error(true, "Cannot update tip cache: modification is not properly setup!"); + return; + } + + tip_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(tip_node)) { + Node *node = stack->skeleton->get_node(tip_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update tip cache: Tip node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update tip cache: Tip node is not in the scene tree. Cannot execute modification!"); + tip_node_cache = node->get_instance_id(); + + execution_error_found = false; + } + } + } +} + +void SkeletonModification3DTwoBoneIK::update_cache_pole() { + if (!is_setup || !stack) { + _print_execution_error(true, "Cannot update pole cache: modification is not properly setup!"); + return; + } + + pole_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(pole_node)) { + Node *node = stack->skeleton->get_node(pole_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update pole cache: Pole node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update pole cache: Pole node is not in the scene tree. Cannot execute modification!"); + pole_node_cache = node->get_instance_id(); + + execution_error_found = false; + } + } + } +} + +void SkeletonModification3DTwoBoneIK::set_target_node(const NodePath &p_target_node) { + target_node = p_target_node; + update_cache_target(); +} + +NodePath SkeletonModification3DTwoBoneIK::get_target_node() const { + return target_node; +} + +void SkeletonModification3DTwoBoneIK::set_use_tip_node(const bool p_use_tip_node) { + use_tip_node = p_use_tip_node; + notify_property_list_changed(); +} + +bool SkeletonModification3DTwoBoneIK::get_use_tip_node() const { + return use_tip_node; +} + +void SkeletonModification3DTwoBoneIK::set_tip_node(const NodePath &p_tip_node) { + tip_node = p_tip_node; + update_cache_tip(); +} + +NodePath SkeletonModification3DTwoBoneIK::get_tip_node() const { + return tip_node; +} + +void SkeletonModification3DTwoBoneIK::set_use_pole_node(const bool p_use_pole_node) { + use_pole_node = p_use_pole_node; + notify_property_list_changed(); +} + +bool SkeletonModification3DTwoBoneIK::get_use_pole_node() const { + return use_pole_node; +} + +void SkeletonModification3DTwoBoneIK::set_pole_node(const NodePath &p_pole_node) { + pole_node = p_pole_node; + update_cache_pole(); +} + +NodePath SkeletonModification3DTwoBoneIK::get_pole_node() const { + return pole_node; +} + +void SkeletonModification3DTwoBoneIK::set_auto_calculate_joint_length(bool p_calculate) { + auto_calculate_joint_length = p_calculate; + if (p_calculate) { + calculate_joint_lengths(); + } + notify_property_list_changed(); +} + +bool SkeletonModification3DTwoBoneIK::get_auto_calculate_joint_length() const { + return auto_calculate_joint_length; +} + +void SkeletonModification3DTwoBoneIK::calculate_joint_lengths() { + if (!is_setup) { + return; // fail silently, as we likely just loaded the scene. + } + ERR_FAIL_COND_MSG(!stack || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot calculate joint lengths!"); + ERR_FAIL_COND_MSG(joint_one_bone_idx <= -1 || joint_two_bone_idx <= -1, + "One of the bones in the TwoBoneIK modification are not set! Cannot calculate joint lengths!"); + + Transform3D bone_one_rest_trans = stack->skeleton->get_bone_global_pose(joint_one_bone_idx); + Transform3D bone_two_rest_trans = stack->skeleton->get_bone_global_pose(joint_two_bone_idx); + + joint_one_length = bone_one_rest_trans.origin.distance_to(bone_two_rest_trans.origin); + + if (use_tip_node) { + if (tip_node_cache.is_null()) { + update_cache_tip(); + WARN_PRINT("Tip cache is out of date. Updating..."); + } + + Node3D *tip = Object::cast_to<Node3D>(ObjectDB::get_instance(tip_node_cache)); + if (tip) { + Transform3D bone_tip_trans = stack->skeleton->world_transform_to_global_pose(tip->get_global_transform()); + joint_two_length = bone_two_rest_trans.origin.distance_to(bone_tip_trans.origin); + } + } else { + // Attempt to use children bones to get the length + Vector<int> bone_two_children = stack->skeleton->get_bone_children(joint_two_bone_idx); + if (bone_two_children.size() > 0) { + joint_two_length = 0; + for (int i = 0; i < bone_two_children.size(); i++) { + joint_two_length += bone_two_rest_trans.origin.distance_to( + stack->skeleton->local_pose_to_global_pose(bone_two_children[i], stack->skeleton->get_bone_rest(bone_two_children[i])).origin); + } + joint_two_length = joint_two_length / bone_two_children.size(); + } else { + WARN_PRINT("TwoBoneIK modification: Cannot auto calculate length for joint 2! Auto setting the length to 1..."); + joint_two_length = 1.0; + } + } + execution_error_found = false; +} + +void SkeletonModification3DTwoBoneIK::set_joint_one_bone_name(String p_bone_name) { + joint_one_bone_name = p_bone_name; + if (stack && stack->skeleton) { + joint_one_bone_idx = stack->skeleton->find_bone(p_bone_name); + } + execution_error_found = false; + notify_property_list_changed(); +} + +String SkeletonModification3DTwoBoneIK::get_joint_one_bone_name() const { + return joint_one_bone_name; +} + +void SkeletonModification3DTwoBoneIK::set_joint_one_bone_idx(int p_bone_idx) { + joint_one_bone_idx = p_bone_idx; + if (stack && stack->skeleton) { + joint_one_bone_name = stack->skeleton->get_bone_name(p_bone_idx); + } + execution_error_found = false; + notify_property_list_changed(); +} + +int SkeletonModification3DTwoBoneIK::get_joint_one_bone_idx() const { + return joint_one_bone_idx; +} + +void SkeletonModification3DTwoBoneIK::set_joint_one_length(real_t p_length) { + joint_one_length = p_length; +} + +real_t SkeletonModification3DTwoBoneIK::get_joint_one_length() const { + return joint_one_length; +} + +void SkeletonModification3DTwoBoneIK::set_joint_two_bone_name(String p_bone_name) { + joint_two_bone_name = p_bone_name; + if (stack && stack->skeleton) { + joint_two_bone_idx = stack->skeleton->find_bone(p_bone_name); + } + execution_error_found = false; + notify_property_list_changed(); +} + +String SkeletonModification3DTwoBoneIK::get_joint_two_bone_name() const { + return joint_two_bone_name; +} + +void SkeletonModification3DTwoBoneIK::set_joint_two_bone_idx(int p_bone_idx) { + joint_two_bone_idx = p_bone_idx; + if (stack && stack->skeleton) { + joint_two_bone_name = stack->skeleton->get_bone_name(p_bone_idx); + } + execution_error_found = false; + notify_property_list_changed(); +} + +int SkeletonModification3DTwoBoneIK::get_joint_two_bone_idx() const { + return joint_two_bone_idx; +} + +void SkeletonModification3DTwoBoneIK::set_joint_two_length(real_t p_length) { + joint_two_length = p_length; +} + +real_t SkeletonModification3DTwoBoneIK::get_joint_two_length() const { + return joint_two_length; +} + +void SkeletonModification3DTwoBoneIK::set_joint_one_roll(real_t p_roll) { + joint_one_roll = p_roll; +} + +real_t SkeletonModification3DTwoBoneIK::get_joint_one_roll() const { + return joint_one_roll; +} + +void SkeletonModification3DTwoBoneIK::set_joint_two_roll(real_t p_roll) { + joint_two_roll = p_roll; +} + +real_t SkeletonModification3DTwoBoneIK::get_joint_two_roll() const { + return joint_two_roll; +} + +void SkeletonModification3DTwoBoneIK::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification3DTwoBoneIK::set_target_node); + ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification3DTwoBoneIK::get_target_node); + + ClassDB::bind_method(D_METHOD("set_use_pole_node", "use_pole_node"), &SkeletonModification3DTwoBoneIK::set_use_pole_node); + ClassDB::bind_method(D_METHOD("get_use_pole_node"), &SkeletonModification3DTwoBoneIK::get_use_pole_node); + ClassDB::bind_method(D_METHOD("set_pole_node", "pole_nodepath"), &SkeletonModification3DTwoBoneIK::set_pole_node); + ClassDB::bind_method(D_METHOD("get_pole_node"), &SkeletonModification3DTwoBoneIK::get_pole_node); + + ClassDB::bind_method(D_METHOD("set_use_tip_node", "use_tip_node"), &SkeletonModification3DTwoBoneIK::set_use_tip_node); + ClassDB::bind_method(D_METHOD("get_use_tip_node"), &SkeletonModification3DTwoBoneIK::get_use_tip_node); + ClassDB::bind_method(D_METHOD("set_tip_node", "tip_nodepath"), &SkeletonModification3DTwoBoneIK::set_tip_node); + ClassDB::bind_method(D_METHOD("get_tip_node"), &SkeletonModification3DTwoBoneIK::get_tip_node); + + ClassDB::bind_method(D_METHOD("set_auto_calculate_joint_length", "auto_calculate_joint_length"), &SkeletonModification3DTwoBoneIK::set_auto_calculate_joint_length); + ClassDB::bind_method(D_METHOD("get_auto_calculate_joint_length"), &SkeletonModification3DTwoBoneIK::get_auto_calculate_joint_length); + + ClassDB::bind_method(D_METHOD("set_joint_one_bone_name", "bone_name"), &SkeletonModification3DTwoBoneIK::set_joint_one_bone_name); + ClassDB::bind_method(D_METHOD("get_joint_one_bone_name"), &SkeletonModification3DTwoBoneIK::get_joint_one_bone_name); + ClassDB::bind_method(D_METHOD("set_joint_one_bone_idx", "bone_idx"), &SkeletonModification3DTwoBoneIK::set_joint_one_bone_idx); + ClassDB::bind_method(D_METHOD("get_joint_one_bone_idx"), &SkeletonModification3DTwoBoneIK::get_joint_one_bone_idx); + ClassDB::bind_method(D_METHOD("set_joint_one_length", "bone_length"), &SkeletonModification3DTwoBoneIK::set_joint_one_length); + ClassDB::bind_method(D_METHOD("get_joint_one_length"), &SkeletonModification3DTwoBoneIK::get_joint_one_length); + + ClassDB::bind_method(D_METHOD("set_joint_two_bone_name", "bone_name"), &SkeletonModification3DTwoBoneIK::set_joint_two_bone_name); + ClassDB::bind_method(D_METHOD("get_joint_two_bone_name"), &SkeletonModification3DTwoBoneIK::get_joint_two_bone_name); + ClassDB::bind_method(D_METHOD("set_joint_two_bone_idx", "bone_idx"), &SkeletonModification3DTwoBoneIK::set_joint_two_bone_idx); + ClassDB::bind_method(D_METHOD("get_joint_two_bone_idx"), &SkeletonModification3DTwoBoneIK::get_joint_two_bone_idx); + ClassDB::bind_method(D_METHOD("set_joint_two_length", "bone_length"), &SkeletonModification3DTwoBoneIK::set_joint_two_length); + ClassDB::bind_method(D_METHOD("get_joint_two_length"), &SkeletonModification3DTwoBoneIK::get_joint_two_length); + + ClassDB::bind_method(D_METHOD("set_joint_one_roll", "roll"), &SkeletonModification3DTwoBoneIK::set_joint_one_roll); + ClassDB::bind_method(D_METHOD("get_joint_one_roll"), &SkeletonModification3DTwoBoneIK::get_joint_one_roll); + ClassDB::bind_method(D_METHOD("set_joint_two_roll", "roll"), &SkeletonModification3DTwoBoneIK::set_joint_two_roll); + ClassDB::bind_method(D_METHOD("get_joint_two_roll"), &SkeletonModification3DTwoBoneIK::get_joint_two_roll); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_target_node", "get_target_node"); + ADD_GROUP("", ""); +} + +SkeletonModification3DTwoBoneIK::SkeletonModification3DTwoBoneIK() { + stack = nullptr; + is_setup = false; +} + +SkeletonModification3DTwoBoneIK::~SkeletonModification3DTwoBoneIK() { +} diff --git a/scene/resources/skeleton_modification_3d_twoboneik.h b/scene/resources/skeleton_modification_3d_twoboneik.h new file mode 100644 index 0000000000..e62d6cc497 --- /dev/null +++ b/scene/resources/skeleton_modification_3d_twoboneik.h @@ -0,0 +1,118 @@ +/*************************************************************************/ +/* skeleton_modification_3d_twoboneik.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/skeleton_modification_3d.h" + +#ifndef SKELETONMODIFICATION3DTWOBONEIK_H +#define SKELETONMODIFICATION3DTWOBONEIK_H + +class SkeletonModification3DTwoBoneIK : public SkeletonModification3D { + GDCLASS(SkeletonModification3DTwoBoneIK, SkeletonModification3D); + +private: + NodePath target_node; + ObjectID target_node_cache; + + bool use_tip_node = false; + NodePath tip_node; + ObjectID tip_node_cache; + + bool use_pole_node = false; + NodePath pole_node; + ObjectID pole_node_cache; + + String joint_one_bone_name = ""; + int joint_one_bone_idx = -1; + String joint_two_bone_name = ""; + int joint_two_bone_idx = -1; + + bool auto_calculate_joint_length = false; + real_t joint_one_length = -1; + real_t joint_two_length = -1; + + real_t joint_one_roll = 0; + real_t joint_two_roll = 0; + + void update_cache_target(); + void update_cache_tip(); + void update_cache_pole(); + +protected: + static void _bind_methods(); + bool _get(const StringName &p_path, Variant &r_ret) const; + bool _set(const StringName &p_path, const Variant &p_value); + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + virtual void _execute(real_t p_delta) override; + virtual void _setup_modification(SkeletonModificationStack3D *p_stack) override; + + void set_target_node(const NodePath &p_target_node); + NodePath get_target_node() const; + + void set_use_tip_node(const bool p_use_tip_node); + bool get_use_tip_node() const; + void set_tip_node(const NodePath &p_tip_node); + NodePath get_tip_node() const; + + void set_use_pole_node(const bool p_use_pole_node); + bool get_use_pole_node() const; + void set_pole_node(const NodePath &p_pole_node); + NodePath get_pole_node() const; + + void set_auto_calculate_joint_length(bool p_calculate); + bool get_auto_calculate_joint_length() const; + void calculate_joint_lengths(); + + void set_joint_one_bone_name(String p_bone_name); + String get_joint_one_bone_name() const; + void set_joint_one_bone_idx(int p_bone_idx); + int get_joint_one_bone_idx() const; + void set_joint_one_length(real_t p_length); + real_t get_joint_one_length() const; + + void set_joint_two_bone_name(String p_bone_name); + String get_joint_two_bone_name() const; + void set_joint_two_bone_idx(int p_bone_idx); + int get_joint_two_bone_idx() const; + void set_joint_two_length(real_t p_length); + real_t get_joint_two_length() const; + + void set_joint_one_roll(real_t p_roll); + real_t get_joint_one_roll() const; + void set_joint_two_roll(real_t p_roll); + real_t get_joint_two_roll() const; + + SkeletonModification3DTwoBoneIK(); + ~SkeletonModification3DTwoBoneIK(); +}; + +#endif //SKELETONMODIFICATION3DTWOBONEIK_H diff --git a/scene/resources/skeleton_modification_stack_3d.cpp b/scene/resources/skeleton_modification_stack_3d.cpp new file mode 100644 index 0000000000..3fce0e5dbd --- /dev/null +++ b/scene/resources/skeleton_modification_stack_3d.cpp @@ -0,0 +1,222 @@ +/*************************************************************************/ +/* skeleton_modification_stack_3d.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "skeleton_modification_stack_3d.h" +#include "scene/3d/skeleton_3d.h" + +/////////////////////////////////////// +// ModificationStack3D +/////////////////////////////////////// + +void SkeletonModificationStack3D::_get_property_list(List<PropertyInfo> *p_list) const { + for (uint32_t i = 0; i < modifications.size(); i++) { + p_list->push_back( + PropertyInfo(Variant::OBJECT, "modifications/" + itos(i), + PROPERTY_HINT_RESOURCE_TYPE, + "SkeletonModification3D", + PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DEFERRED_SET_RESOURCE | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE)); + } +} + +bool SkeletonModificationStack3D::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.begins_with("modifications/")) { + int mod_idx = path.get_slicec('/', 1).to_int(); + set_modification(mod_idx, p_value); + return true; + } + return true; +} + +bool SkeletonModificationStack3D::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.begins_with("modifications/")) { + int mod_idx = path.get_slicec('/', 1).to_int(); + r_ret = get_modification(mod_idx); + return true; + } + return true; +} + +void SkeletonModificationStack3D::setup() { + if (is_setup) { + return; + } + + if (skeleton != nullptr) { + is_setup = true; + for (uint32_t i = 0; i < modifications.size(); i++) { + if (!modifications[i].is_valid()) { + continue; + } + modifications[i]->_setup_modification(this); + } + } else { + WARN_PRINT("Cannot setup SkeletonModificationStack3D: no skeleton set!"); + } +} + +void SkeletonModificationStack3D::execute(real_t p_delta, int p_execution_mode) { + ERR_FAIL_COND_MSG(!is_setup || skeleton == nullptr || is_queued_for_deletion(), + "Modification stack is not properly setup and therefore cannot execute!"); + + if (!skeleton->is_inside_tree()) { + ERR_PRINT_ONCE("Skeleton is not inside SceneTree! Cannot execute modification!"); + return; + } + + if (!enabled) { + return; + } + + for (uint32_t i = 0; i < modifications.size(); i++) { + if (!modifications[i].is_valid()) { + continue; + } + + if (modifications[i]->get_execution_mode() == p_execution_mode) { + modifications[i]->_execute(p_delta); + } + } +} + +void SkeletonModificationStack3D::enable_all_modifications(bool p_enabled) { + for (uint32_t i = 0; i < modifications.size(); i++) { + if (!modifications[i].is_valid()) { + continue; + } + modifications[i]->set_enabled(p_enabled); + } +} + +Ref<SkeletonModification3D> SkeletonModificationStack3D::get_modification(int p_mod_idx) const { + const int modifications_size = modifications.size(); + ERR_FAIL_INDEX_V(p_mod_idx, modifications_size, nullptr); + return modifications[p_mod_idx]; +} + +void SkeletonModificationStack3D::add_modification(Ref<SkeletonModification3D> p_mod) { + p_mod->_setup_modification(this); + modifications.push_back(p_mod); +} + +void SkeletonModificationStack3D::delete_modification(int p_mod_idx) { + const int modifications_size = modifications.size(); + ERR_FAIL_INDEX(p_mod_idx, modifications_size); + modifications.remove(p_mod_idx); +} + +void SkeletonModificationStack3D::set_modification(int p_mod_idx, Ref<SkeletonModification3D> p_mod) { + const int modifications_size = modifications.size(); + ERR_FAIL_INDEX(p_mod_idx, modifications_size); + + if (p_mod == nullptr) { + modifications.remove(p_mod_idx); + } else { + p_mod->_setup_modification(this); + modifications[p_mod_idx] = p_mod; + } +} + +void SkeletonModificationStack3D::set_modification_count(int p_count) { + modifications.resize(p_count); + notify_property_list_changed(); +} + +int SkeletonModificationStack3D::get_modification_count() const { + return modifications.size(); +} + +void SkeletonModificationStack3D::set_skeleton(Skeleton3D *p_skeleton) { + skeleton = p_skeleton; +} + +Skeleton3D *SkeletonModificationStack3D::get_skeleton() const { + return skeleton; +} + +bool SkeletonModificationStack3D::get_is_setup() const { + return is_setup; +} + +void SkeletonModificationStack3D::set_enabled(bool p_enabled) { + enabled = p_enabled; + + if (!enabled && is_setup && skeleton != nullptr) { + skeleton->clear_bones_local_pose_override(); + } +} + +bool SkeletonModificationStack3D::get_enabled() const { + return enabled; +} + +void SkeletonModificationStack3D::set_strength(real_t p_strength) { + ERR_FAIL_COND_MSG(p_strength < 0, "Strength cannot be less than zero!"); + ERR_FAIL_COND_MSG(p_strength > 1, "Strength cannot be more than one!"); + strength = p_strength; +} + +real_t SkeletonModificationStack3D::get_strength() const { + return strength; +} + +void SkeletonModificationStack3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("setup"), &SkeletonModificationStack3D::setup); + ClassDB::bind_method(D_METHOD("execute", "delta", "execution_mode"), &SkeletonModificationStack3D::execute); + + ClassDB::bind_method(D_METHOD("enable_all_modifications", "enabled"), &SkeletonModificationStack3D::enable_all_modifications); + ClassDB::bind_method(D_METHOD("get_modification", "mod_idx"), &SkeletonModificationStack3D::get_modification); + ClassDB::bind_method(D_METHOD("add_modification", "modification"), &SkeletonModificationStack3D::add_modification); + ClassDB::bind_method(D_METHOD("delete_modification", "mod_idx"), &SkeletonModificationStack3D::delete_modification); + ClassDB::bind_method(D_METHOD("set_modification", "mod_idx", "modification"), &SkeletonModificationStack3D::set_modification); + + ClassDB::bind_method(D_METHOD("set_modification_count"), &SkeletonModificationStack3D::set_modification_count); + ClassDB::bind_method(D_METHOD("get_modification_count"), &SkeletonModificationStack3D::get_modification_count); + + ClassDB::bind_method(D_METHOD("get_is_setup"), &SkeletonModificationStack3D::get_is_setup); + + ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &SkeletonModificationStack3D::set_enabled); + ClassDB::bind_method(D_METHOD("get_enabled"), &SkeletonModificationStack3D::get_enabled); + + ClassDB::bind_method(D_METHOD("set_strength", "strength"), &SkeletonModificationStack3D::set_strength); + ClassDB::bind_method(D_METHOD("get_strength"), &SkeletonModificationStack3D::get_strength); + + ClassDB::bind_method(D_METHOD("get_skeleton"), &SkeletonModificationStack3D::get_skeleton); + + 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"); +} + +SkeletonModificationStack3D::SkeletonModificationStack3D() { +} diff --git a/scene/resources/skeleton_modification_stack_3d.h b/scene/resources/skeleton_modification_stack_3d.h new file mode 100644 index 0000000000..cbc8d4e0b9 --- /dev/null +++ b/scene/resources/skeleton_modification_stack_3d.h @@ -0,0 +1,91 @@ +/*************************************************************************/ +/* skeleton_modification_stack_3d.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SKELETONMODIFICATIONSTACK3D_H +#define SKELETONMODIFICATIONSTACK3D_H + +#include "core/templates/local_vector.h" +#include "scene/3d/skeleton_3d.h" + +class Skeleton3D; +class SkeletonModification3D; + +class SkeletonModificationStack3D : public Resource { + GDCLASS(SkeletonModificationStack3D, Resource); + friend class Skeleton3D; + friend class SkeletonModification3D; + +protected: + static void _bind_methods(); + virtual void _get_property_list(List<PropertyInfo> *p_list) const; + virtual bool _set(const StringName &p_path, const Variant &p_value); + virtual bool _get(const StringName &p_path, Variant &r_ret) const; + +public: + Skeleton3D *skeleton = nullptr; + bool is_setup = false; + bool enabled = false; + real_t strength = 1.0; + + enum EXECUTION_MODE { + execution_mode_process, + execution_mode_physics_process, + }; + + LocalVector<Ref<SkeletonModification3D>> modifications = LocalVector<Ref<SkeletonModification3D>>(); + int modifications_count = 0; + + virtual void setup(); + virtual void execute(real_t p_delta, int p_execution_mode); + + void enable_all_modifications(bool p_enable); + Ref<SkeletonModification3D> get_modification(int p_mod_idx) const; + void add_modification(Ref<SkeletonModification3D> p_mod); + void delete_modification(int p_mod_idx); + void set_modification(int p_mod_idx, Ref<SkeletonModification3D> p_mod); + + void set_modification_count(int p_count); + int get_modification_count() const; + + void set_skeleton(Skeleton3D *p_skeleton); + Skeleton3D *get_skeleton() const; + + bool get_is_setup() const; + + void set_enabled(bool p_enabled); + bool get_enabled() const; + + void set_strength(real_t p_strength); + real_t get_strength() const; + + SkeletonModificationStack3D(); +}; + +#endif // SKELETONMODIFICATIONSTACK3D_H diff --git a/scene/resources/sky_material.cpp b/scene/resources/sky_material.cpp index 9d79c22159..39082b6f7a 100644 --- a/scene/resources/sky_material.cpp +++ b/scene/resources/sky_material.cpp @@ -30,6 +30,11 @@ #include "sky_material.h" +#include "core/version.h" + +Mutex ProceduralSkyMaterial::shader_mutex; +RID ProceduralSkyMaterial::shader; + void ProceduralSkyMaterial::set_sky_top_color(const Color &p_sky_top) { sky_top_color = p_sky_top; RS::get_singleton()->material_set_param(_get_material(), "sky_top_color", sky_top_color.to_linear()); @@ -128,7 +133,17 @@ Shader::Mode ProceduralSkyMaterial::get_shader_mode() const { return Shader::MODE_SKY; } +RID ProceduralSkyMaterial::get_rid() const { + _update_shader(); + if (!shader_set) { + RS::get_singleton()->material_set_shader(_get_material(), shader); + shader_set = true; + } + return _get_material(); +} + RID ProceduralSkyMaterial::get_shader_rid() const { + _update_shader(); return shader; } @@ -180,72 +195,92 @@ void ProceduralSkyMaterial::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sun_curve", PROPERTY_HINT_EXP_EASING), "set_sun_curve", "get_sun_curve"); } -ProceduralSkyMaterial::ProceduralSkyMaterial() { - String code = "shader_type sky;\n\n"; - - code += "uniform vec4 sky_top_color : hint_color = vec4(0.35, 0.46, 0.71, 1.0);\n"; - code += "uniform vec4 sky_horizon_color : hint_color = vec4(0.55, 0.69, 0.81, 1.0);\n"; - code += "uniform float sky_curve : hint_range(0, 1) = 0.09;\n"; - code += "uniform float sky_energy = 1.0;\n\n"; - code += "uniform vec4 ground_bottom_color : hint_color = vec4(0.12, 0.12, 0.13, 1.0);\n"; - code += "uniform vec4 ground_horizon_color : hint_color = vec4(0.37, 0.33, 0.31, 1.0);\n"; - code += "uniform float ground_curve : hint_range(0, 1) = 0.02;\n"; - code += "uniform float ground_energy = 1.0;\n\n"; - code += "uniform float sun_angle_max = 1.74;\n"; - code += "uniform float sun_curve : hint_range(0, 1) = 0.05;\n\n"; - code += "void sky() {\n"; - code += "\tfloat v_angle = acos(clamp(EYEDIR.y, -1.0, 1.0));\n"; - code += "\tfloat c = (1.0 - v_angle / (PI * 0.5));\n"; - code += "\tvec3 sky = mix(sky_horizon_color.rgb, sky_top_color.rgb, clamp(1.0 - pow(1.0 - c, 1.0 / sky_curve), 0.0, 1.0));\n"; - code += "\tsky *= sky_energy;\n"; - code += "\tif (LIGHT0_ENABLED) {\n"; - code += "\t\tfloat sun_angle = acos(dot(LIGHT0_DIRECTION, EYEDIR));\n"; - code += "\t\tif (sun_angle < LIGHT0_SIZE) {\n"; - code += "\t\t\tsky = LIGHT0_COLOR * LIGHT0_ENERGY;\n"; - code += "\t\t} else if (sun_angle < sun_angle_max) {\n"; - code += "\t\t\tfloat c2 = (sun_angle - LIGHT0_SIZE) / (sun_angle_max - LIGHT0_SIZE);\n"; - code += "\t\t\tsky = mix(LIGHT0_COLOR * LIGHT0_ENERGY, sky, clamp(1.0 - pow(1.0 - c2, 1.0 / sun_curve), 0.0, 1.0));\n"; - code += "\t\t}\n"; - code += "\t}\n"; - code += "\tif (LIGHT1_ENABLED) {\n"; - code += "\t\tfloat sun_angle = acos(dot(LIGHT1_DIRECTION, EYEDIR));\n"; - code += "\t\tif (sun_angle < LIGHT1_SIZE) {\n"; - code += "\t\t\tsky = LIGHT1_COLOR * LIGHT1_ENERGY;\n"; - code += "\t\t} else if (sun_angle < sun_angle_max) {\n"; - code += "\t\t\tfloat c2 = (sun_angle - LIGHT1_SIZE) / (sun_angle_max - LIGHT1_SIZE);\n"; - code += "\t\t\tsky = mix(LIGHT1_COLOR * LIGHT1_ENERGY, sky, clamp(1.0 - pow(1.0 - c2, 1.0 / sun_curve), 0.0, 1.0));\n"; - code += "\t\t}\n"; - code += "\t}\n"; - code += "\tif (LIGHT2_ENABLED) {\n"; - code += "\t\tfloat sun_angle = acos(dot(LIGHT2_DIRECTION, EYEDIR));\n"; - code += "\t\tif (sun_angle < LIGHT2_SIZE) {\n"; - code += "\t\t\tsky = LIGHT2_COLOR * LIGHT2_ENERGY;\n"; - code += "\t\t} else if (sun_angle < sun_angle_max) {\n"; - code += "\t\t\tfloat c2 = (sun_angle - LIGHT2_SIZE) / (sun_angle_max - LIGHT2_SIZE);\n"; - code += "\t\t\tsky = mix(LIGHT2_COLOR * LIGHT2_ENERGY, sky, clamp(1.0 - pow(1.0 - c2, 1.0 / sun_curve), 0.0, 1.0));\n"; - code += "\t\t}\n"; - code += "\t}\n"; - code += "\tif (LIGHT3_ENABLED) {\n"; - code += "\t\tfloat sun_angle = acos(dot(LIGHT3_DIRECTION, EYEDIR));\n"; - code += "\t\tif (sun_angle < LIGHT3_SIZE) {\n"; - code += "\t\t\tsky = LIGHT3_COLOR * LIGHT3_ENERGY;\n"; - code += "\t\t} else if (sun_angle < sun_angle_max) {\n"; - code += "\t\t\tfloat c2 = (sun_angle - LIGHT3_SIZE) / (sun_angle_max - LIGHT3_SIZE);\n"; - code += "\t\t\tsky = mix(LIGHT3_COLOR * LIGHT3_ENERGY, sky, clamp(1.0 - pow(1.0 - c2, 1.0 / sun_curve), 0.0, 1.0));\n"; - code += "\t\t}\n"; - code += "\t}\n"; - code += "\tc = (v_angle - (PI * 0.5)) / (PI * 0.5);\n"; - code += "\tvec3 ground = mix(ground_horizon_color.rgb, ground_bottom_color.rgb, clamp(1.0 - pow(1.0 - c, 1.0 / ground_curve), 0.0, 1.0));\n"; - code += "\tground *= ground_energy;\n"; - code += "\tCOLOR = mix(ground, sky, step(0.0, EYEDIR.y));\n"; - code += "}\n"; - - shader = RS::get_singleton()->shader_create(); - - RS::get_singleton()->shader_set_code(shader, code); - - RS::get_singleton()->material_set_shader(_get_material(), shader); +void ProceduralSkyMaterial::cleanup_shader() { + if (shader.is_valid()) { + RS::get_singleton()->free(shader); + } +} + +void ProceduralSkyMaterial::_update_shader() { + shader_mutex.lock(); + if (shader.is_null()) { + shader = RS::get_singleton()->shader_create(); + + // Add a comment to describe the shader origin (useful when converting to ShaderMaterial). + RS::get_singleton()->shader_set_code(shader, R"( +// NOTE: Shader automatically converted from )" VERSION_NAME " " VERSION_FULL_CONFIG R"('s ProceduralSkyMaterial. + +shader_type sky; + +uniform vec4 sky_top_color : hint_color = vec4(0.35, 0.46, 0.71, 1.0); +uniform vec4 sky_horizon_color : hint_color = vec4(0.55, 0.69, 0.81, 1.0); +uniform float sky_curve : hint_range(0, 1) = 0.09; +uniform float sky_energy = 1.0; +uniform vec4 ground_bottom_color : hint_color = vec4(0.12, 0.12, 0.13, 1.0); +uniform vec4 ground_horizon_color : hint_color = vec4(0.37, 0.33, 0.31, 1.0); +uniform float ground_curve : hint_range(0, 1) = 0.02; +uniform float ground_energy = 1.0; +uniform float sun_angle_max = 1.74; +uniform float sun_curve : hint_range(0, 1) = 0.05; + +void sky() { + float v_angle = acos(clamp(EYEDIR.y, -1.0, 1.0)); + float c = (1.0 - v_angle / (PI * 0.5)); + vec3 sky = mix(sky_horizon_color.rgb, sky_top_color.rgb, clamp(1.0 - pow(1.0 - c, 1.0 / sky_curve), 0.0, 1.0)); + sky *= sky_energy; + + if (LIGHT0_ENABLED) { + float sun_angle = acos(dot(LIGHT0_DIRECTION, EYEDIR)); + if (sun_angle < LIGHT0_SIZE) { + sky = LIGHT0_COLOR * LIGHT0_ENERGY; + } else if (sun_angle < sun_angle_max) { + float c2 = (sun_angle - LIGHT0_SIZE) / (sun_angle_max - LIGHT0_SIZE); + sky = mix(LIGHT0_COLOR * LIGHT0_ENERGY, sky, clamp(1.0 - pow(1.0 - c2, 1.0 / sun_curve), 0.0, 1.0)); + } + } + + if (LIGHT1_ENABLED) { + float sun_angle = acos(dot(LIGHT1_DIRECTION, EYEDIR)); + if (sun_angle < LIGHT1_SIZE) { + sky = LIGHT1_COLOR * LIGHT1_ENERGY; + } else if (sun_angle < sun_angle_max) { + float c2 = (sun_angle - LIGHT1_SIZE) / (sun_angle_max - LIGHT1_SIZE); + sky = mix(LIGHT1_COLOR * LIGHT1_ENERGY, sky, clamp(1.0 - pow(1.0 - c2, 1.0 / sun_curve), 0.0, 1.0)); + } + } + + if (LIGHT2_ENABLED) { + float sun_angle = acos(dot(LIGHT2_DIRECTION, EYEDIR)); + if (sun_angle < LIGHT2_SIZE) { + sky = LIGHT2_COLOR * LIGHT2_ENERGY; + } else if (sun_angle < sun_angle_max) { + float c2 = (sun_angle - LIGHT2_SIZE) / (sun_angle_max - LIGHT2_SIZE); + sky = mix(LIGHT2_COLOR * LIGHT2_ENERGY, sky, clamp(1.0 - pow(1.0 - c2, 1.0 / sun_curve), 0.0, 1.0)); + } + } + + if (LIGHT3_ENABLED) { + float sun_angle = acos(dot(LIGHT3_DIRECTION, EYEDIR)); + if (sun_angle < LIGHT3_SIZE) { + sky = LIGHT3_COLOR * LIGHT3_ENERGY; + } else if (sun_angle < sun_angle_max) { + float c2 = (sun_angle - LIGHT3_SIZE) / (sun_angle_max - LIGHT3_SIZE); + sky = mix(LIGHT3_COLOR * LIGHT3_ENERGY, sky, clamp(1.0 - pow(1.0 - c2, 1.0 / sun_curve), 0.0, 1.0)); + } + } + + c = (v_angle - (PI * 0.5)) / (PI * 0.5); + vec3 ground = mix(ground_horizon_color.rgb, ground_bottom_color.rgb, clamp(1.0 - pow(1.0 - c, 1.0 / ground_curve), 0.0, 1.0)); + ground *= ground_energy; + + COLOR = mix(ground, sky, step(0.0, EYEDIR.y)); +} +)"); + } + shader_mutex.unlock(); +} +ProceduralSkyMaterial::ProceduralSkyMaterial() { set_sky_top_color(Color(0.35, 0.46, 0.71)); set_sky_horizon_color(Color(0.55, 0.69, 0.81)); set_sky_curve(0.09); @@ -261,7 +296,6 @@ ProceduralSkyMaterial::ProceduralSkyMaterial() { } ProceduralSkyMaterial::~ProceduralSkyMaterial() { - RS::get_singleton()->free(shader); RS::get_singleton()->material_set_shader(_get_material(), RID()); } @@ -270,7 +304,8 @@ ProceduralSkyMaterial::~ProceduralSkyMaterial() { void PanoramaSkyMaterial::set_panorama(const Ref<Texture2D> &p_panorama) { panorama = p_panorama; - RS::get_singleton()->material_set_param(_get_material(), "source_panorama", panorama); + RID tex_rid = p_panorama.is_valid() ? p_panorama->get_rid() : RID(); + RS::get_singleton()->material_set_param(_get_material(), "source_panorama", tex_rid); } Ref<Texture2D> PanoramaSkyMaterial::get_panorama() const { @@ -285,7 +320,17 @@ Shader::Mode PanoramaSkyMaterial::get_shader_mode() const { return Shader::MODE_SKY; } +RID PanoramaSkyMaterial::get_rid() const { + _update_shader(); + if (!shader_set) { + RS::get_singleton()->material_set_shader(_get_material(), shader); + shader_set = true; + } + return _get_material(); +} + RID PanoramaSkyMaterial::get_shader_rid() const { + _update_shader(); return shader; } @@ -296,23 +341,41 @@ void PanoramaSkyMaterial::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "panorama", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_panorama", "get_panorama"); } -PanoramaSkyMaterial::PanoramaSkyMaterial() { - String code = "shader_type sky;\n\n"; +Mutex PanoramaSkyMaterial::shader_mutex; +RID PanoramaSkyMaterial::shader; + +void PanoramaSkyMaterial::cleanup_shader() { + if (shader.is_valid()) { + RS::get_singleton()->free(shader); + } +} + +void PanoramaSkyMaterial::_update_shader() { + shader_mutex.lock(); + if (shader.is_null()) { + shader = RS::get_singleton()->shader_create(); - code += "uniform sampler2D source_panorama : filter_linear;\n"; - code += "void sky() {\n"; - code += "\tCOLOR = texture(source_panorama, SKY_COORDS).rgb;\n"; - code += "}"; + // Add a comment to describe the shader origin (useful when converting to ShaderMaterial). + RS::get_singleton()->shader_set_code(shader, R"( +// NOTE: Shader automatically converted from )" VERSION_NAME " " VERSION_FULL_CONFIG R"('s PanoramaSkyMaterial. - shader = RS::get_singleton()->shader_create(); +shader_type sky; - RS::get_singleton()->shader_set_code(shader, code); +uniform sampler2D source_panorama : filter_linear, hint_albedo; - RS::get_singleton()->material_set_shader(_get_material(), shader); +void sky() { + COLOR = texture(source_panorama, SKY_COORDS).rgb; +} +)"); + } + + shader_mutex.unlock(); +} + +PanoramaSkyMaterial::PanoramaSkyMaterial() { } PanoramaSkyMaterial::~PanoramaSkyMaterial() { - RS::get_singleton()->free(shader); RS::get_singleton()->material_set_shader(_get_material(), RID()); } @@ -411,7 +474,8 @@ float PhysicalSkyMaterial::get_dither_strength() const { void PhysicalSkyMaterial::set_night_sky(const Ref<Texture2D> &p_night_sky) { night_sky = p_night_sky; - RS::get_singleton()->material_set_param(_get_material(), "night_sky", night_sky); + RID tex_rid = p_night_sky.is_valid() ? p_night_sky->get_rid() : RID(); + RS::get_singleton()->material_set_param(_get_material(), "night_sky", tex_rid); } Ref<Texture2D> PhysicalSkyMaterial::get_night_sky() const { @@ -426,10 +490,23 @@ Shader::Mode PhysicalSkyMaterial::get_shader_mode() const { return Shader::MODE_SKY; } +RID PhysicalSkyMaterial::get_rid() const { + _update_shader(); + if (!shader_set) { + RS::get_singleton()->material_set_shader(_get_material(), shader); + shader_set = true; + } + return _get_material(); +} + RID PhysicalSkyMaterial::get_shader_rid() const { + _update_shader(); return shader; } +Mutex PhysicalSkyMaterial::shader_mutex; +RID PhysicalSkyMaterial::shader; + void PhysicalSkyMaterial::_bind_methods() { ClassDB::bind_method(D_METHOD("set_rayleigh_coefficient", "rayleigh"), &PhysicalSkyMaterial::set_rayleigh_coefficient); ClassDB::bind_method(D_METHOD("get_rayleigh_coefficient"), &PhysicalSkyMaterial::get_rayleigh_coefficient); @@ -481,106 +558,120 @@ void PhysicalSkyMaterial::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "night_sky", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_night_sky", "get_night_sky"); } -PhysicalSkyMaterial::PhysicalSkyMaterial() { - String code = "shader_type sky;\n\n"; - - code += "uniform float rayleigh : hint_range(0, 64) = 2.0;\n"; - code += "uniform vec4 rayleigh_color : hint_color = vec4(0.056, 0.14, 0.3, 1.0);\n"; - code += "uniform float mie : hint_range(0, 1) = 0.005;\n"; - code += "uniform float mie_eccentricity : hint_range(-1, 1) = 0.8;\n"; - code += "uniform vec4 mie_color : hint_color = vec4(0.36, 0.56, 0.82, 1.0);\n\n"; - - code += "uniform float turbidity : hint_range(0, 1000) = 10.0;\n"; - code += "uniform float sun_disk_scale : hint_range(0, 360) = 1.0;\n"; - code += "uniform vec4 ground_color : hint_color = vec4(1.0);\n"; - code += "uniform float exposure : hint_range(0, 128) = 0.1;\n"; - code += "uniform float dither_strength : hint_range(0, 10) = 1.0;\n\n"; - - code += "uniform sampler2D night_sky : hint_black;"; - - code += "const vec3 UP = vec3( 0.0, 1.0, 0.0 );\n\n"; - - code += "// Sun constants\n"; - code += "const float SUN_ENERGY = 1000.0;\n\n"; - - code += "// optical length at zenith for molecules\n"; - code += "const float rayleigh_zenith_size = 8.4e3;\n"; - code += "const float mie_zenith_size = 1.25e3;\n\n"; - - code += "float henyey_greenstein(float cos_theta, float g) {\n"; - code += "\tconst float k = 0.0795774715459;\n"; - code += "\treturn k * (1.0 - g * g) / (pow(1.0 + g * g - 2.0 * g * cos_theta, 1.5));\n"; - code += "}\n\n"; - - code += "// From: https://www.shadertoy.com/view/4sfGzS credit to iq\n"; - code += "float hash(vec3 p) {\n"; - code += "\tp = fract( p * 0.3183099 + 0.1 );\n"; - code += "\tp *= 17.0;\n"; - code += "\treturn fract(p.x * p.y * p.z * (p.x + p.y + p.z));\n"; - code += "}\n\n"; - - code += "void sky() {\n"; - code += "\tif (LIGHT0_ENABLED) {\n"; - code += "\t\tfloat zenith_angle = clamp( dot(UP, normalize(LIGHT0_DIRECTION)), -1.0, 1.0 );\n"; - code += "\t\tfloat sun_energy = max(0.0, 1.0 - exp(-((PI * 0.5) - acos(zenith_angle)))) * SUN_ENERGY * LIGHT0_ENERGY;\n"; - code += "\t\tfloat sun_fade = 1.0 - clamp(1.0 - exp(LIGHT0_DIRECTION.y), 0.0, 1.0);\n\n"; - - code += "\t\t// rayleigh coefficients\n"; - code += "\t\tfloat rayleigh_coefficient = rayleigh - ( 1.0 * ( 1.0 - sun_fade ) );\n"; - code += "\t\tvec3 rayleigh_beta = rayleigh_coefficient * rayleigh_color.rgb * 0.0001;\n"; - code += "\t\t// mie coefficients from Preetham\n"; - code += "\t\tvec3 mie_beta = turbidity * mie * mie_color.rgb * 0.000434;\n\n"; - - code += "\t\t// optical length\n"; - code += "\t\tfloat zenith = acos(max(0.0, dot(UP, EYEDIR)));\n"; - code += "\t\tfloat optical_mass = 1.0 / (cos(zenith) + 0.15 * pow(93.885 - degrees(zenith), -1.253));\n"; - code += "\t\tfloat rayleigh_scatter = rayleigh_zenith_size * optical_mass;\n"; - code += "\t\tfloat mie_scatter = mie_zenith_size * optical_mass;\n\n"; - - code += "\t\t// light extinction based on thickness of atmosphere\n"; - code += "\t\tvec3 extinction = exp(-(rayleigh_beta * rayleigh_scatter + mie_beta * mie_scatter));\n\n"; - - code += "\t\t// in scattering\n"; - code += "\t\tfloat cos_theta = dot(EYEDIR, normalize(LIGHT0_DIRECTION));\n\n"; - - code += "\t\tfloat rayleigh_phase = (3.0 / (16.0 * PI)) * (1.0 + pow(cos_theta * 0.5 + 0.5, 2.0));\n"; - code += "\t\tvec3 betaRTheta = rayleigh_beta * rayleigh_phase;\n\n"; - - code += "\t\tfloat mie_phase = henyey_greenstein(cos_theta, mie_eccentricity);\n"; - code += "\t\tvec3 betaMTheta = mie_beta * mie_phase;\n\n"; - - code += "\t\tvec3 Lin = pow(sun_energy * ((betaRTheta + betaMTheta) / (rayleigh_beta + mie_beta)) * (1.0 - extinction), vec3(1.5));\n"; - code += "\t\t// Hack from https://github.com/mrdoob/three.js/blob/master/examples/jsm/objects/Sky.js\n"; - code += "\t\tLin *= mix(vec3(1.0), pow(sun_energy * ((betaRTheta + betaMTheta) / (rayleigh_beta + mie_beta)) * extinction, vec3(0.5)), clamp(pow(1.0 - zenith_angle, 5.0), 0.0, 1.0));\n\n"; - - code += "\t\t// Hack in the ground color\n"; - code += "\t\tLin *= mix(ground_color.rgb, vec3(1.0), smoothstep(-0.1, 0.1, dot(UP, EYEDIR)));\n\n"; - - code += "\t\t// Solar disk and out-scattering\n"; - code += "\t\tfloat sunAngularDiameterCos = cos(LIGHT0_SIZE * sun_disk_scale);\n"; - code += "\t\tfloat sunAngularDiameterCos2 = cos(LIGHT0_SIZE * sun_disk_scale*0.5);\n"; - code += "\t\tfloat sundisk = smoothstep(sunAngularDiameterCos, sunAngularDiameterCos2, cos_theta);\n"; - code += "\t\tvec3 L0 = (sun_energy * 1900.0 * extinction) * sundisk * LIGHT0_COLOR;\n"; - code += "\t\tL0 += texture(night_sky, SKY_COORDS).xyz * extinction;\n\n"; - - code += "\t\tvec3 color = (Lin + L0) * 0.04;\n"; - code += "\t\tCOLOR = pow(color, vec3(1.0 / (1.2 + (1.2 * sun_fade))));\n"; - code += "\t\tCOLOR *= exposure;\n"; - code += "\t\t// Make optional, eliminates banding\n"; - code += "\t\tCOLOR += (hash(EYEDIR * 1741.9782) * 0.08 - 0.04) * 0.016 * dither_strength;\n"; - code += "\t} else {\n"; - code += "\t\t// There is no sun, so display night_sky and nothing else\n"; - code += "\t\tCOLOR = texture(night_sky, SKY_COORDS).xyz * 0.04;\n"; - code += "\t\tCOLOR *= exposure;\n"; - code += "\t}\n"; - code += "}\n"; - - shader = RS::get_singleton()->shader_create(); - - RS::get_singleton()->shader_set_code(shader, code); - - RS::get_singleton()->material_set_shader(_get_material(), shader); +void PhysicalSkyMaterial::cleanup_shader() { + if (shader.is_valid()) { + RS::get_singleton()->free(shader); + } +} + +void PhysicalSkyMaterial::_update_shader() { + shader_mutex.lock(); + if (shader.is_null()) { + shader = RS::get_singleton()->shader_create(); + + // Add a comment to describe the shader origin (useful when converting to ShaderMaterial). + RS::get_singleton()->shader_set_code(shader, R"( +// NOTE: Shader automatically converted from )" VERSION_NAME " " VERSION_FULL_CONFIG R"('s PhysicalSkyMaterial. + +shader_type sky; +uniform float rayleigh : hint_range(0, 64) = 2.0; +uniform vec4 rayleigh_color : hint_color = vec4(0.056, 0.14, 0.3, 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.36, 0.56, 0.82, 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(1.0); +uniform float exposure : hint_range(0, 128) = 0.1; +uniform float dither_strength : hint_range(0, 10) = 1.0; + +uniform sampler2D night_sky : hint_black_albedo; + +const vec3 UP = vec3( 0.0, 1.0, 0.0 ); + +// Sun constants +const float SUN_ENERGY = 1000.0; + +// Optical length at zenith for molecules. +const float rayleigh_zenith_size = 8.4e3; +const float mie_zenith_size = 1.25e3; + +float henyey_greenstein(float cos_theta, float g) { + const float k = 0.0795774715459; + 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)); +} + +void sky() { + if (LIGHT0_ENABLED) { + float zenith_angle = clamp( dot(UP, normalize(LIGHT0_DIRECTION)), -1.0, 1.0 ); + float sun_energy = max(0.0, 1.0 - exp(-((PI * 0.5) - acos(zenith_angle)))) * SUN_ENERGY * LIGHT0_ENERGY; + float sun_fade = 1.0 - clamp(1.0 - exp(LIGHT0_DIRECTION.y), 0.0, 1.0); + + // Rayleigh coefficients. + float rayleigh_coefficient = rayleigh - ( 1.0 * ( 1.0 - sun_fade ) ); + vec3 rayleigh_beta = rayleigh_coefficient * rayleigh_color.rgb * 0.0001; + // mie coefficients from Preetham + vec3 mie_beta = turbidity * mie * mie_color.rgb * 0.000434; + + // Optical length. + float zenith = acos(max(0.0, dot(UP, EYEDIR))); + float optical_mass = 1.0 / (cos(zenith) + 0.15 * pow(93.885 - degrees(zenith), -1.253)); + float rayleigh_scatter = rayleigh_zenith_size * optical_mass; + float mie_scatter = mie_zenith_size * optical_mass; + + // Light extinction based on thickness of atmosphere. + vec3 extinction = exp(-(rayleigh_beta * rayleigh_scatter + mie_beta * mie_scatter)); + + // In scattering. + float cos_theta = dot(EYEDIR, normalize(LIGHT0_DIRECTION)); + + float rayleigh_phase = (3.0 / (16.0 * PI)) * (1.0 + pow(cos_theta * 0.5 + 0.5, 2.0)); + vec3 betaRTheta = rayleigh_beta * rayleigh_phase; + + float mie_phase = henyey_greenstein(cos_theta, mie_eccentricity); + vec3 betaMTheta = mie_beta * mie_phase; + + vec3 Lin = pow(sun_energy * ((betaRTheta + betaMTheta) / (rayleigh_beta + mie_beta)) * (1.0 - extinction), vec3(1.5)); + // Hack from https://github.com/mrdoob/three.js/blob/master/examples/jsm/objects/Sky.js + Lin *= mix(vec3(1.0), pow(sun_energy * ((betaRTheta + betaMTheta) / (rayleigh_beta + mie_beta)) * extinction, vec3(0.5)), clamp(pow(1.0 - zenith_angle, 5.0), 0.0, 1.0)); + + // Hack in the ground color. + Lin *= mix(ground_color.rgb, vec3(1.0), smoothstep(-0.1, 0.1, dot(UP, EYEDIR))); + + // Solar disk and out-scattering. + float sunAngularDiameterCos = cos(LIGHT0_SIZE * sun_disk_scale); + float sunAngularDiameterCos2 = cos(LIGHT0_SIZE * sun_disk_scale*0.5); + float sundisk = smoothstep(sunAngularDiameterCos, sunAngularDiameterCos2, cos_theta); + vec3 L0 = (sun_energy * 1900.0 * extinction) * sundisk * LIGHT0_COLOR; + L0 += texture(night_sky, SKY_COORDS).xyz * extinction; + + 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; + } else { + // There is no sun, so display night_sky and nothing else. + COLOR = texture(night_sky, SKY_COORDS).xyz * 0.04; + COLOR *= exposure; + } +} +)"); + } + + shader_mutex.unlock(); +} + +PhysicalSkyMaterial::PhysicalSkyMaterial() { set_rayleigh_coefficient(2.0); set_rayleigh_color(Color(0.056, 0.14, 0.3)); set_mie_coefficient(0.005); @@ -594,5 +685,4 @@ PhysicalSkyMaterial::PhysicalSkyMaterial() { } PhysicalSkyMaterial::~PhysicalSkyMaterial() { - RS::get_singleton()->free(shader); } diff --git a/scene/resources/sky_material.h b/scene/resources/sky_material.h index 8fe015519d..63e730617b 100644 --- a/scene/resources/sky_material.h +++ b/scene/resources/sky_material.h @@ -51,7 +51,10 @@ private: float sun_angle_max; float sun_curve; - RID shader; + static Mutex shader_mutex; + static RID shader; + static void _update_shader(); + mutable bool shader_set = false; protected: static void _bind_methods(); @@ -90,6 +93,9 @@ public: virtual Shader::Mode get_shader_mode() const override; virtual RID get_shader_rid() const override; + virtual RID get_rid() const override; + + static void cleanup_shader(); ProceduralSkyMaterial(); ~ProceduralSkyMaterial(); @@ -103,7 +109,11 @@ class PanoramaSkyMaterial : public Material { private: Ref<Texture2D> panorama; - RID shader; + + static Mutex shader_mutex; + static RID shader; + static void _update_shader(); + mutable bool shader_set = false; protected: static void _bind_methods(); @@ -115,6 +125,9 @@ public: virtual Shader::Mode get_shader_mode() const override; virtual RID get_shader_rid() const override; + virtual RID get_rid() const override; + + static void cleanup_shader(); PanoramaSkyMaterial(); ~PanoramaSkyMaterial(); @@ -127,7 +140,8 @@ class PhysicalSkyMaterial : public Material { GDCLASS(PhysicalSkyMaterial, Material); private: - RID shader; + static Mutex shader_mutex; + static RID shader; float rayleigh; Color rayleigh_color; @@ -140,6 +154,8 @@ private: float exposure; float dither_strength; Ref<Texture2D> night_sky; + static void _update_shader(); + mutable bool shader_set = false; protected: static void _bind_methods(); @@ -182,6 +198,9 @@ public: virtual Shader::Mode get_shader_mode() const override; virtual RID get_shader_rid() const override; + static void cleanup_shader(); + virtual RID get_rid() const override; + PhysicalSkyMaterial(); ~PhysicalSkyMaterial(); }; diff --git a/scene/resources/sprite_frames.cpp b/scene/resources/sprite_frames.cpp index df80084c5c..140c6f821f 100644 --- a/scene/resources/sprite_frames.cpp +++ b/scene/resources/sprite_frames.cpp @@ -100,8 +100,8 @@ Vector<String> SpriteFrames::_get_animation_list() const { Vector<String> ret; List<StringName> al; get_animation_list(&al); - for (List<StringName>::Element *E = al.front(); E; E = E->next()) { - ret.push_back(E->get()); + for (const StringName &E : al) { + ret.push_back(E); } return ret; @@ -122,14 +122,14 @@ Vector<String> SpriteFrames::get_animation_names() const { return names; } -void SpriteFrames::set_animation_speed(const StringName &p_anim, float p_fps) { +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); ERR_FAIL_COND_MSG(!E, "Animation '" + String(p_anim) + "' doesn't exist."); E->get().speed = p_fps; } -float SpriteFrames::get_animation_speed(const StringName &p_anim) const { +double SpriteFrames::get_animation_speed(const StringName &p_anim) const { const Map<StringName, Anim>::Element *E = animations.find(p_anim); ERR_FAIL_COND_V_MSG(!E, 0, "Animation '" + String(p_anim) + "' doesn't exist."); return E->get().speed; diff --git a/scene/resources/sprite_frames.h b/scene/resources/sprite_frames.h index 282c5f20ab..fdfd6af5ab 100644 --- a/scene/resources/sprite_frames.h +++ b/scene/resources/sprite_frames.h @@ -37,7 +37,7 @@ class SpriteFrames : public Resource { GDCLASS(SpriteFrames, Resource); struct Anim { - float speed = 5.0; + double speed = 5.0; bool loop = true; Vector<Ref<Texture2D>> frames; }; @@ -64,8 +64,8 @@ public: void get_animation_list(List<StringName> *r_animations) const; Vector<String> get_animation_names() const; - void set_animation_speed(const StringName &p_anim, float p_fps); - float get_animation_speed(const StringName &p_anim) const; + void set_animation_speed(const StringName &p_anim, double p_fps); + double get_animation_speed(const StringName &p_anim) const; void set_animation_loop(const StringName &p_anim, bool p_loop); bool get_animation_loop(const StringName &p_anim) const; diff --git a/scene/resources/style_box.cpp b/scene/resources/style_box.cpp index 87371224e0..3381043d29 100644 --- a/scene/resources/style_box.cpp +++ b/scene/resources/style_box.cpp @@ -87,9 +87,6 @@ void StyleBox::_bind_methods() { ClassDB::bind_method(D_METHOD("set_default_margin", "margin", "offset"), &StyleBox::set_default_margin); ClassDB::bind_method(D_METHOD("get_default_margin", "margin"), &StyleBox::get_default_margin); - //ClassDB::bind_method(D_METHOD("set_default_margin"),&StyleBox::set_default_margin); - //ClassDB::bind_method(D_METHOD("get_default_margin"),&StyleBox::get_default_margin); - ClassDB::bind_method(D_METHOD("get_margin", "margin"), &StyleBox::get_margin); ClassDB::bind_method(D_METHOD("get_minimum_size"), &StyleBox::get_minimum_size); ClassDB::bind_method(D_METHOD("get_center_size"), &StyleBox::get_center_size); @@ -464,12 +461,12 @@ bool StyleBoxFlat::is_anti_aliased() const { return anti_aliased; } -void StyleBoxFlat::set_aa_size(const int &p_aa_size) { - aa_size = CLAMP(p_aa_size, 1, 5); +void StyleBoxFlat::set_aa_size(const real_t p_aa_size) { + aa_size = CLAMP(p_aa_size, 0.01, 10); emit_changed(); } -int StyleBoxFlat::get_aa_size() const { +real_t StyleBoxFlat::get_aa_size() const { return aa_size; } @@ -486,31 +483,32 @@ Size2 StyleBoxFlat::get_center_size() const { return Size2(); } -inline void set_inner_corner_radius(const Rect2 style_rect, const Rect2 inner_rect, const int corner_radius[4], int *inner_corner_radius) { - int border_left = inner_rect.position.x - style_rect.position.x; - int border_top = inner_rect.position.y - style_rect.position.y; - int border_right = style_rect.size.width - inner_rect.size.width - border_left; - int border_bottom = style_rect.size.height - inner_rect.size.height - border_top; +inline void set_inner_corner_radius(const Rect2 style_rect, const Rect2 inner_rect, const real_t corner_radius[4], real_t *inner_corner_radius) { + real_t border_left = inner_rect.position.x - style_rect.position.x; + real_t border_top = inner_rect.position.y - style_rect.position.y; + real_t border_right = style_rect.size.width - inner_rect.size.width - border_left; + real_t border_bottom = style_rect.size.height - inner_rect.size.height - border_top; + + real_t rad; - int rad; - //tl + // Top left. rad = MIN(border_top, border_left); inner_corner_radius[0] = MAX(corner_radius[0] - rad, 0); - //tr + // Top right; rad = MIN(border_top, border_right); inner_corner_radius[1] = MAX(corner_radius[1] - rad, 0); - //br + // Bottom right. rad = MIN(border_bottom, border_right); inner_corner_radius[2] = MAX(corner_radius[2] - rad, 0); - //bl + // Bottom left. rad = MIN(border_bottom, border_left); inner_corner_radius[3] = MAX(corner_radius[3] - rad, 0); } -inline void draw_ring(Vector<Vector2> &verts, Vector<int> &indices, Vector<Color> &colors, const Rect2 &style_rect, const int corner_radius[4], +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) { int vert_offset = verts.size(); if (!vert_offset) { @@ -519,17 +517,17 @@ inline void draw_ring(Vector<Vector2> &verts, Vector<int> &indices, Vector<Color int adapted_corner_detail = (corner_radius[0] == 0 && corner_radius[1] == 0 && corner_radius[2] == 0 && corner_radius[3] == 0) ? 1 : corner_detail; - int ring_corner_radius[4]; + real_t ring_corner_radius[4]; set_inner_corner_radius(style_rect, ring_rect, corner_radius, ring_corner_radius); - //corner radius center points + // Corner radius center points. Vector<Point2> outer_points; outer_points.push_back(ring_rect.position + Vector2(ring_corner_radius[0], ring_corner_radius[0])); //tl outer_points.push_back(Point2(ring_rect.position.x + ring_rect.size.x - ring_corner_radius[1], ring_rect.position.y + ring_corner_radius[1])); //tr outer_points.push_back(ring_rect.position + ring_rect.size - Vector2(ring_corner_radius[2], ring_corner_radius[2])); //br outer_points.push_back(Point2(ring_rect.position.x + ring_corner_radius[3], ring_rect.position.y + ring_rect.size.y - ring_corner_radius[3])); //bl - int inner_corner_radius[4]; + real_t inner_corner_radius[4]; set_inner_corner_radius(style_rect, inner_rect, corner_radius, inner_corner_radius); Vector<Point2> inner_points; @@ -538,11 +536,11 @@ inline void draw_ring(Vector<Vector2> &verts, Vector<int> &indices, Vector<Color inner_points.push_back(inner_rect.position + inner_rect.size - Vector2(inner_corner_radius[2], inner_corner_radius[2])); //br inner_points.push_back(Point2(inner_rect.position.x + inner_corner_radius[3], inner_rect.position.y + inner_rect.size.y - inner_corner_radius[3])); //bl - //calculate the vert array + // Calculate the vertices. for (int corner_index = 0; corner_index < 4; corner_index++) { for (int detail = 0; detail <= adapted_corner_detail; detail++) { for (int inner_outer = 0; inner_outer < 2; inner_outer++) { - float radius; + real_t radius; Color color; Point2 corner_point; if (inner_outer == 0) { @@ -564,7 +562,7 @@ inline void draw_ring(Vector<Vector2> &verts, Vector<int> &indices, Vector<Color int ring_vert_count = verts.size() - vert_offset; - //fill the indices and the colors for the border + // Fill the indices and the colors for the border. for (int i = 0; i < ring_vert_count; i++) { indices.push_back(vert_offset + ((i + 0) % ring_vert_count)); indices.push_back(vert_offset + ((i + 2) % ring_vert_count)); @@ -572,14 +570,14 @@ inline void draw_ring(Vector<Vector2> &verts, Vector<int> &indices, Vector<Color } if (fill_center) { - //fill the indices and the colors for the center + //Fill the indices and the colors for the center. for (int index = 0; index < ring_vert_count / 2; index += 2) { int i = index; - //poly 1 + // Polygon 1. indices.push_back(vert_offset + i); indices.push_back(vert_offset + ring_vert_count - 4 - i); indices.push_back(vert_offset + i + 2); - //poly 2 + // Polygon 2. indices.push_back(vert_offset + i); indices.push_back(vert_offset + ring_vert_count - 2 - i); indices.push_back(vert_offset + ring_vert_count - 4 - i); @@ -587,20 +585,20 @@ inline void draw_ring(Vector<Vector2> &verts, Vector<int> &indices, Vector<Color } } -inline void adapt_values(int p_index_a, int p_index_b, int *adapted_values, const int *p_values, const real_t p_width, const int p_max_a, const int p_max_b) { +inline void adapt_values(int p_index_a, int p_index_b, real_t *adapted_values, const real_t *p_values, const real_t p_width, const real_t p_max_a, const real_t p_max_b) { if (p_values[p_index_a] + p_values[p_index_b] > p_width) { - float factor; - int newValue; + real_t factor; + real_t new_value; - factor = (float)p_width / (float)(p_values[p_index_a] + p_values[p_index_b]); + factor = (real_t)p_width / (real_t)(p_values[p_index_a] + p_values[p_index_b]); - newValue = (int)(p_values[p_index_a] * factor); - if (newValue < adapted_values[p_index_a]) { - adapted_values[p_index_a] = newValue; + new_value = (p_values[p_index_a] * factor); + if (new_value < adapted_values[p_index_a]) { + adapted_values[p_index_a] = new_value; } - newValue = (int)(p_values[p_index_b] * factor); - if (newValue < adapted_values[p_index_b]) { - adapted_values[p_index_b] = newValue; + new_value = (p_values[p_index_b] * factor); + if (new_value < adapted_values[p_index_b]) { + adapted_values[p_index_b] = new_value; } } else { adapted_values[p_index_a] = MIN(p_values[p_index_a], adapted_values[p_index_a]); @@ -623,7 +621,6 @@ Rect2 StyleBoxFlat::get_draw_rect(const Rect2 &p_rect) const { } void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { - //PREPARATIONS bool draw_border = (border_width[0] > 0) || (border_width[1] > 0) || (border_width[2] > 0) || (border_width[3] > 0); bool draw_shadow = (shadow_size > 0); if (!draw_border && !draw_center && !draw_shadow) { @@ -637,7 +634,6 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { 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; - float aa_size_grow = 0.5 * ((float)aa_size + 1.0); bool blend_on = blend_border && draw_border; @@ -645,15 +641,15 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { Color border_color_blend = (draw_center ? bg_color : border_color_alpha); Color border_color_inner = blend_on ? border_color_blend : border_color; - //adapt borders (prevent weird overlapping/glitchy drawings) - int width = MAX(style_rect.size.width, 0); - int height = MAX(style_rect.size.height, 0); - int adapted_border[4] = { INT_MAX, INT_MAX, INT_MAX, INT_MAX }; + // Adapt borders (prevent weird overlapping/glitchy drawings). + real_t width = MAX(style_rect.size.width, 0); + real_t height = MAX(style_rect.size.height, 0); + real_t adapted_border[4] = { 1000000.0, 1000000.0, 1000000.0, 1000000.0 }; adapt_values(SIDE_TOP, SIDE_BOTTOM, adapted_border, border_width, height, height, height); adapt_values(SIDE_LEFT, SIDE_RIGHT, adapted_border, border_width, width, width, width); - //adapt corners (prevent weird overlapping/glitchy drawings) - int adapted_corner[4] = { INT_MAX, INT_MAX, INT_MAX, INT_MAX }; + // Adapt corners (prevent weird overlapping/glitchy drawings). + real_t adapted_corner[4] = { 1000000.0, 1000000.0, 1000000.0, 1000000.0 }; adapt_values(CORNER_TOP_RIGHT, CORNER_BOTTOM_RIGHT, adapted_corner, corner_radius, height, height - adapted_border[SIDE_BOTTOM], height - adapted_border[SIDE_TOP]); adapt_values(CORNER_TOP_LEFT, CORNER_BOTTOM_LEFT, adapted_corner, corner_radius, height, height - adapted_border[SIDE_BOTTOM], height - adapted_border[SIDE_TOP]); adapt_values(CORNER_TOP_LEFT, CORNER_TOP_RIGHT, adapted_corner, corner_radius, width, width - adapted_border[SIDE_RIGHT], width - adapted_border[SIDE_LEFT]); @@ -665,7 +661,7 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { if (aa_on) { for (int i = 0; i < 4; i++) { if (border_width[i] > 0) { - border_style_rect = border_style_rect.grow_side((Side)i, -aa_size_grow); + border_style_rect = border_style_rect.grow_side((Side)i, -aa_size); } } } @@ -675,7 +671,7 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { Vector<Color> colors; Vector<Point2> uvs; - //DRAW SHADOW + // Create shadow if (draw_shadow) { Rect2 shadow_inner_rect = style_rect; shadow_inner_rect.position += shadow_offset; @@ -694,35 +690,35 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { } } - //DRAW border - if (draw_border) { + // 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); } - //DRAW INFILL + // 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); } if (aa_on) { - int aa_border_width[4]; - int aa_fill_width[4]; + real_t aa_border_width[4]; + real_t aa_fill_width[4]; if (draw_border) { for (int i = 0; i < 4; i++) { if (border_width[i] > 0) { - aa_border_width[i] = aa_size_grow; + aa_border_width[i] = aa_size; aa_fill_width[i] = 0; } else { aa_border_width[i] = 0; - aa_fill_width[i] = aa_size_grow; + aa_fill_width[i] = aa_size; } } } else { for (int i = 0; i < 4; i++) { aa_border_width[i] = 0; - aa_fill_width[i] = aa_size_grow; + aa_fill_width[i] = aa_size; } } @@ -731,45 +727,58 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { if (draw_center) { if (!blend_on && draw_border) { - //DRAW INFILL WITHIN BORDER AA + Rect2 infill_inner_rect_aa = infill_inner_rect.grow_individual(aa_border_width[SIDE_LEFT], aa_border_width[SIDE_TOP], + 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, infill_inner_rect, bg_color, bg_color, corner_detail, true); + infill_inner_rect_aa, infill_inner_rect_aa, bg_color, bg_color, corner_detail, true); } if (!blend_on || !draw_border) { - Rect2 infill_aa_rect = infill_rect.grow_individual(aa_fill_width[SIDE_LEFT], aa_fill_width[SIDE_TOP], + Rect2 infill_rect_aa = infill_rect.grow_individual(aa_fill_width[SIDE_LEFT], aa_fill_width[SIDE_TOP], aa_fill_width[SIDE_RIGHT], aa_fill_width[SIDE_BOTTOM]); Color alpha_bg = Color(bg_color.r, bg_color.g, bg_color.b, 0); - //INFILL AA + // Create infill fake AA gradient. draw_ring(verts, indices, colors, style_rect, adapted_corner, - infill_aa_rect, infill_rect, bg_color, alpha_bg, corner_detail); + infill_rect_aa, infill_rect, bg_color, alpha_bg, corner_detail); } } if (draw_border) { + Rect2 infill_rect_aa = infill_rect.grow_individual(aa_border_width[SIDE_LEFT], aa_border_width[SIDE_TOP], + aa_border_width[SIDE_RIGHT], aa_border_width[SIDE_BOTTOM]); + Rect2 style_rect_aa = style_rect.grow_individual(aa_border_width[SIDE_LEFT], aa_border_width[SIDE_TOP], + aa_border_width[SIDE_RIGHT], aa_border_width[SIDE_BOTTOM]); + Rect2 border_style_rect_aa = border_style_rect.grow_individual(aa_border_width[SIDE_LEFT], aa_border_width[SIDE_TOP], + aa_border_width[SIDE_RIGHT], aa_border_width[SIDE_BOTTOM]); + + // 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); + if (!blend_on) { - //DRAW INNER BORDER AA + // Create inner border fake AA gradient. draw_ring(verts, indices, colors, border_style_rect, adapted_corner, - infill_rect, infill_inner_rect, border_color_blend, border_color, corner_detail); + infill_rect_aa, infill_rect, border_color_blend, border_color, corner_detail); } - //DRAW OUTER BORDER AA + // Create outer border fake AA gradient. draw_ring(verts, indices, colors, border_style_rect, adapted_corner, - style_rect, border_style_rect, border_color, border_color_alpha, corner_detail); + style_rect_aa, border_style_rect_aa, border_color, border_color_alpha, corner_detail); } } - //COMPUTE UV COORDINATES - Rect2 uv_rect = style_rect.grow(aa_on ? aa_size_grow : 0); + // Compute UV coordinates. + Rect2 uv_rect = style_rect.grow(aa_on ? aa_size : 0); uvs.resize(verts.size()); for (int i = 0; i < verts.size(); i++) { uvs.write[i].x = (verts[i].x - uv_rect.position.x) / uv_rect.size.width; uvs.write[i].y = (verts[i].y - uv_rect.position.y) / uv_rect.size.height; } - //DRAWING + // Draw stylebox. RenderingServer *vs = RenderingServer::get_singleton(); vs->canvas_item_add_triangle_array(p_canvas_item, indices, verts, colors, uvs); } @@ -869,7 +878,7 @@ void StyleBoxFlat::_bind_methods() { ADD_GROUP("Anti Aliasing", "anti_aliasing_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "anti_aliasing"), "set_anti_aliased", "is_anti_aliased"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "anti_aliasing_size", PROPERTY_HINT_RANGE, "1,5,1"), "set_aa_size", "get_aa_size"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "anti_aliasing_size", PROPERTY_HINT_RANGE, "0.01,10,0.001"), "set_aa_size", "get_aa_size"); } StyleBoxFlat::StyleBoxFlat() {} diff --git a/scene/resources/style_box.h b/scene/resources/style_box.h index dd5c873a00..a6cd5b7fb7 100644 --- a/scene/resources/style_box.h +++ b/scene/resources/style_box.h @@ -143,9 +143,9 @@ class StyleBoxFlat : public StyleBox { Color shadow_color = Color(0, 0, 0, 0.6); Color border_color = Color(0.8, 0.8, 0.8); - int border_width[4] = {}; - int expand_margin[4] = {}; - int corner_radius[4] = {}; + real_t border_width[4] = {}; + real_t expand_margin[4] = {}; + real_t corner_radius[4] = {}; bool draw_center = true; bool blend_border = false; @@ -154,7 +154,7 @@ class StyleBoxFlat : public StyleBox { int corner_detail = 8; int shadow_size = 0; Point2 shadow_offset; - int aa_size = 1; + real_t aa_size = 0.625; protected: virtual float get_style_margin(Side p_side) const override; @@ -162,27 +162,21 @@ protected: void _validate_property(PropertyInfo &property) const override; public: - //Color void set_bg_color(const Color &p_color); Color get_bg_color() const; - //Border Color void set_border_color(const Color &p_color); Color get_border_color() const; - //BORDER - //width void set_border_width_all(int p_size); int get_border_width_min() const; void set_border_width(Side p_side, int p_width); int get_border_width(Side p_side) const; - //blend void set_border_blend(bool p_blend); bool get_border_blend() const; - //CORNER void set_corner_radius_all(int radius); void set_corner_radius_individual(const int radius_top_left, const int radius_top_right, const int radius_bottom_right, const int radius_bottom_left); @@ -192,17 +186,14 @@ public: void set_corner_detail(const int &p_corner_detail); int get_corner_detail() const; - //EXPANDS void set_expand_margin_size(Side p_expand_side, float p_size); void set_expand_margin_size_all(float p_expand_margin_size); void set_expand_margin_size_individual(float p_left, float p_top, float p_right, float p_bottom); float get_expand_margin_size(Side p_expand_side) const; - //DRAW CENTER void set_draw_center(bool p_enabled); bool is_draw_center_enabled() const; - //SHADOW void set_shadow_color(const Color &p_color); Color get_shadow_color() const; @@ -212,12 +203,10 @@ public: void set_shadow_offset(const Point2 &p_offset); Point2 get_shadow_offset() const; - //ANTI_ALIASING void set_anti_aliased(const bool &p_anti_aliased); bool is_anti_aliased() const; - //tempAA - void set_aa_size(const int &p_aa_size); - int get_aa_size() const; + void set_aa_size(const real_t p_aa_size); + real_t get_aa_size() const; virtual Size2 get_center_size() const override; @@ -228,7 +217,7 @@ public: ~StyleBoxFlat(); }; -// just used to draw lines. +// Just used to draw lines. class StyleBoxLine : public StyleBox { GDCLASS(StyleBoxLine, StyleBox); Color color; diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp index 9f8c35b668..d5e370568d 100644 --- a/scene/resources/surface_tool.cpp +++ b/scene/resources/surface_tool.cpp @@ -409,7 +409,7 @@ Array SurfaceTool::commit_to_arrays() { for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - const Color &c = v.custom[idx]; + const Color &c = v.custom[fmt]; w[idx * 4 + 0] = CLAMP(int32_t(c.r * 255.0), 0, 255); w[idx * 4 + 1] = CLAMP(int32_t(c.g * 255.0), 0, 255); w[idx * 4 + 2] = CLAMP(int32_t(c.b * 255.0), 0, 255); @@ -426,7 +426,7 @@ Array SurfaceTool::commit_to_arrays() { for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - const Color &c = v.custom[idx]; + const Color &c = v.custom[fmt]; w[idx * 4 + 0] = uint8_t(int8_t(CLAMP(int32_t(c.r * 127.0), -128, 127))); w[idx * 4 + 1] = uint8_t(int8_t(CLAMP(int32_t(c.g * 127.0), -128, 127))); w[idx * 4 + 2] = uint8_t(int8_t(CLAMP(int32_t(c.b * 127.0), -128, 127))); @@ -443,7 +443,7 @@ Array SurfaceTool::commit_to_arrays() { for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - const Color &c = v.custom[idx]; + const Color &c = v.custom[fmt]; w[idx * 2 + 0] = Math::make_half_float(c.r); w[idx * 2 + 1] = Math::make_half_float(c.g); } @@ -458,7 +458,7 @@ Array SurfaceTool::commit_to_arrays() { for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - const Color &c = v.custom[idx]; + const Color &c = v.custom[fmt]; w[idx * 4 + 0] = Math::make_half_float(c.r); w[idx * 4 + 1] = Math::make_half_float(c.g); w[idx * 4 + 2] = Math::make_half_float(c.b); @@ -475,7 +475,7 @@ Array SurfaceTool::commit_to_arrays() { for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - const Color &c = v.custom[idx]; + const Color &c = v.custom[fmt]; w[idx] = c.r; } @@ -489,7 +489,7 @@ Array SurfaceTool::commit_to_arrays() { for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - const Color &c = v.custom[idx]; + const Color &c = v.custom[fmt]; w[idx * 2 + 0] = c.r; w[idx * 2 + 1] = c.g; } @@ -504,7 +504,7 @@ Array SurfaceTool::commit_to_arrays() { for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - const Color &c = v.custom[idx]; + const Color &c = v.custom[fmt]; w[idx * 3 + 0] = c.r; w[idx * 3 + 1] = c.g; w[idx * 3 + 2] = c.b; @@ -520,7 +520,7 @@ Array SurfaceTool::commit_to_arrays() { for (uint32_t idx = 0; idx < vertex_array.size(); idx++) { const Vertex &v = vertex_array[idx]; - const Color &c = v.custom[idx]; + const Color &c = v.custom[fmt]; w[idx * 4 + 0] = c.r; w[idx * 4 + 1] = c.g; w[idx * 4 + 2] = c.b; @@ -679,6 +679,9 @@ void SurfaceTool::_create_list(const Ref<Mesh> &p_existing, int p_surface, Local _create_list_from_arrays(arr, r_vertex, r_index, lformat); } +static const uint32_t custom_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 custom_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 }; + void SurfaceTool::create_vertex_array_from_triangle_arrays(const Array &p_arrays, LocalVector<SurfaceTool::Vertex> &ret, uint32_t *r_format) { ret.clear(); @@ -733,8 +736,6 @@ void SurfaceTool::create_vertex_array_from_triangle_arrays(const Array &p_arrays if (warr.size()) { lformat |= RS::ARRAY_FORMAT_WEIGHTS; } - static const uint32_t custom_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 custom_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++) { ERR_CONTINUE_MSG(p_arrays[RS::ARRAY_CUSTOM0 + i].get_type() == Variant::PACKED_BYTE_ARRAY, "Extracting Byte/Half formats is not supported"); @@ -832,6 +833,12 @@ void SurfaceTool::create_from_triangle_arrays(const Array &p_arrays) { clear(); primitive = Mesh::PRIMITIVE_TRIANGLES; _create_list_from_arrays(p_arrays, &vertex_array, &index_array, format); + + for (int j = 0; j < RS::ARRAY_CUSTOM_COUNT; j++) { + if (format & custom_mask[j]) { + last_custom_format[j] = (CustomFormat)((format >> custom_shift[j]) & RS::ARRAY_FORMAT_CUSTOM_MASK); + } + } } void SurfaceTool::create_from(const Ref<Mesh> &p_existing, int p_surface) { @@ -841,6 +848,12 @@ void SurfaceTool::create_from(const Ref<Mesh> &p_existing, int p_surface) { primitive = p_existing->surface_get_primitive_type(p_surface); _create_list(p_existing, p_surface, &vertex_array, &index_array, format); material = p_existing->surface_get_material(p_surface); + + for (int j = 0; j < RS::ARRAY_CUSTOM_COUNT; j++) { + if (format & custom_mask[j]) { + last_custom_format[j] = (CustomFormat)((format >> custom_shift[j]) & RS::ARRAY_FORMAT_CUSTOM_MASK); + } + } } void SurfaceTool::create_from_blend_shape(const Ref<Mesh> &p_existing, int p_surface, const String &p_blend_shape_name) { @@ -863,6 +876,12 @@ void SurfaceTool::create_from_blend_shape(const Ref<Mesh> &p_existing, int p_sur Array mesh = arr[shape_idx]; ERR_FAIL_COND(mesh.size() != RS::ARRAY_MAX); _create_list_from_arrays(arr[shape_idx], &vertex_array, &index_array, format); + + for (int j = 0; j < RS::ARRAY_CUSTOM_COUNT; j++) { + if (format & custom_mask[j]) { + last_custom_format[j] = (CustomFormat)((format >> custom_shift[j]) & RS::ARRAY_FORMAT_CUSTOM_MASK); + } + } } void SurfaceTool::append_from(const Ref<Mesh> &p_existing, int p_surface, const Transform3D &p_xform) { @@ -878,6 +897,16 @@ void SurfaceTool::append_from(const Ref<Mesh> &p_existing, int p_surface, const LocalVector<int> nindices; _create_list(p_existing, p_surface, &nvertices, &nindices, nformat); format |= nformat; + + 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; + } + } int vfrom = vertex_array.size(); for (uint32_t vi = 0; vi < nvertices.size(); vi++) { @@ -1120,6 +1149,7 @@ SurfaceTool::CustomFormat SurfaceTool::get_custom_format(int p_index) const { void SurfaceTool::optimize_indices_for_cache() { ERR_FAIL_COND(optimize_vertex_cache_func == nullptr); ERR_FAIL_COND(index_array.size() == 0); + ERR_FAIL_COND(index_array.size() % 3 != 0); LocalVector old_index_array = index_array; memset(index_array.ptr(), 0, index_array.size() * sizeof(int)); diff --git a/scene/resources/syntax_highlighter.cpp b/scene/resources/syntax_highlighter.cpp index 9dd00849f4..52a3abf74d 100644 --- a/scene/resources/syntax_highlighter.cpp +++ b/scene/resources/syntax_highlighter.cpp @@ -43,12 +43,10 @@ Dictionary SyntaxHighlighter::get_line_syntax_highlighting(int p_line) { return color_map; } - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_get_line_syntax_highlighting")) { - color_map = si->call("_get_line_syntax_highlighting", p_line); - } else { - color_map = _get_line_syntax_highlighting(p_line); + if (!GDVIRTUAL_CALL(_get_line_syntax_highlighting, p_line, color_map)) { + color_map = _get_line_syntax_highlighting_impl(p_line); } + highlighting_cache[p_line] = color_map; return color_map; } @@ -69,9 +67,7 @@ void SyntaxHighlighter::_lines_edited_from(int p_from_line, int p_to_line) { void SyntaxHighlighter::clear_highlighting_cache() { highlighting_cache.clear(); - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_clear_highlighting_cache")) { - si->call("_clear_highlighting_cache"); + if (GDVIRTUAL_CALL(_clear_highlighting_cache)) { return; } _clear_highlighting_cache(); @@ -83,9 +79,7 @@ void SyntaxHighlighter::update_cache() { if (text_edit == nullptr) { return; } - ScriptInstance *si = get_script_instance(); - if (si && si->has_method("_update_cache")) { - si->call("_update_cache"); + if (GDVIRTUAL_CALL(_update_cache)) { return; } _update_cache(); @@ -115,9 +109,9 @@ void SyntaxHighlighter::_bind_methods() { ClassDB::bind_method(D_METHOD("clear_highlighting_cache"), &SyntaxHighlighter::clear_highlighting_cache); ClassDB::bind_method(D_METHOD("get_text_edit"), &SyntaxHighlighter::get_text_edit); - BIND_VMETHOD(MethodInfo(Variant::DICTIONARY, "_get_line_syntax_highlighting", PropertyInfo(Variant::INT, "line"))); - BIND_VMETHOD(MethodInfo("_clear_highlighting_cache")); - BIND_VMETHOD(MethodInfo("_update_cache")); + GDVIRTUAL_BIND(_get_line_syntax_highlighting, "line") + GDVIRTUAL_BIND(_clear_highlighting_cache) + GDVIRTUAL_BIND(_update_cache) } //////////////////////////////////////////////////////////////////////////////// @@ -130,7 +124,7 @@ static bool _is_hex_symbol(char32_t c) { return ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); } -Dictionary CodeHighlighter::_get_line_syntax_highlighting(int p_line) { +Dictionary CodeHighlighter::_get_line_syntax_highlighting_impl(int p_line) { Dictionary color_map; bool prev_is_char = false; @@ -405,7 +399,7 @@ void CodeHighlighter::_clear_highlighting_cache() { } void CodeHighlighter::_update_cache() { - font_color = text_edit->get_theme_color("font_color"); + font_color = text_edit->get_theme_color(SNAME("font_color")); } void CodeHighlighter::add_keyword_color(const String &p_keyword, const Color &p_color) { @@ -529,8 +523,8 @@ void CodeHighlighter::set_color_regions(const Dictionary &p_color_regions) { List<Variant> keys; p_color_regions.get_key_list(&keys); - for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { - String key = E->get(); + for (const Variant &E : keys) { + String key = E; String start_key = key.get_slice(" ", 0); String end_key = key.get_slice_count(" ") > 1 ? key.get_slice(" ", 1) : String(); diff --git a/scene/resources/syntax_highlighter.h b/scene/resources/syntax_highlighter.h index f3964b0c8f..0fe39ccff6 100644 --- a/scene/resources/syntax_highlighter.h +++ b/scene/resources/syntax_highlighter.h @@ -32,6 +32,8 @@ #define SYNTAX_HIGHLIGHTER_H #include "core/io/resource.h" +#include "core/object/gdvirtual.gen.inc" +#include "core/object/script_language.h" class TextEdit; @@ -48,9 +50,12 @@ protected: static void _bind_methods(); + GDVIRTUAL1RC(Dictionary, _get_line_syntax_highlighting, int) + GDVIRTUAL0(_clear_highlighting_cache) + GDVIRTUAL0(_update_cache) public: Dictionary get_line_syntax_highlighting(int p_line); - virtual Dictionary _get_line_syntax_highlighting(int p_line) { return Dictionary(); } + virtual Dictionary _get_line_syntax_highlighting_impl(int p_line) { return Dictionary(); } void clear_highlighting_cache(); virtual void _clear_highlighting_cache() {} @@ -93,7 +98,7 @@ protected: static void _bind_methods(); public: - virtual Dictionary _get_line_syntax_highlighting(int p_line) override; + virtual Dictionary _get_line_syntax_highlighting_impl(int p_line) override; virtual void _clear_highlighting_cache() override; virtual void _update_cache() override; diff --git a/scene/resources/text_line.cpp b/scene/resources/text_line.cpp index f1eff6e84f..d2f38ba836 100644 --- a/scene/resources/text_line.cpp +++ b/scene/resources/text_line.cpp @@ -56,8 +56,8 @@ 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"), &TextLine::add_string, DEFVAL(Dictionary()), DEFVAL("")); - ClassDB::bind_method(D_METHOD("add_object", "key", "size", "inline_align", "length"), &TextLine::add_object, DEFVAL(VALIGN_CENTER), DEFVAL(1)); - ClassDB::bind_method(D_METHOD("resize_object", "key", "size", "inline_align"), &TextLine::resize_object, DEFVAL(VALIGN_CENTER)); + ClassDB::bind_method(D_METHOD("add_object", "key", "size", "inline_align", "length"), &TextLine::add_object, DEFVAL(INLINE_ALIGN_CENTER), DEFVAL(1)); + ClassDB::bind_method(D_METHOD("resize_object", "key", "size", "inline_align"), &TextLine::resize_object, DEFVAL(INLINE_ALIGN_CENTER)); ClassDB::bind_method(D_METHOD("set_width", "width"), &TextLine::set_width); ClassDB::bind_method(D_METHOD("get_width"), &TextLine::get_width); @@ -76,6 +76,11 @@ void TextLine::_bind_methods() { 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"); + 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); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior"); + ClassDB::bind_method(D_METHOD("get_objects"), &TextLine::get_objects); ClassDB::bind_method(D_METHOD("get_object_rect", "key"), &TextLine::get_object_rect); @@ -93,6 +98,12 @@ 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() { @@ -100,7 +111,38 @@ void TextLine::_shape() { if (!tab_stops.is_empty()) { TS->shaped_text_tab_align(rid, tab_stops); } - if (align == HALIGN_FILL) { + + uint8_t overrun_flags = TextServer::OVERRUN_NO_TRIMMING; + if (overrun_behavior != 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; + break; + case OVERRUN_TRIM_ELLIPSIS: + overrun_flags |= TextServer::OVERRUN_TRIM; + overrun_flags |= TextServer::OVERRUN_ADD_ELLIPSIS; + break; + case OVERRUN_TRIM_WORD: + overrun_flags |= TextServer::OVERRUN_TRIM; + overrun_flags |= TextServer::OVERRUN_TRIM_WORD_ONLY; + break; + case OVERRUN_TRIM_CHAR: + overrun_flags |= TextServer::OVERRUN_TRIM; + break; + case OVERRUN_NO_TRIMMING: + break; + } + + if (align == HALIGN_FILL) { + TS->shaped_text_fit_to_width(rid, width, flags); + overrun_flags |= 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); + } + } else if (align == HALIGN_FILL) { TS->shaped_text_fit_to_width(rid, width, flags); } dirty = false; @@ -169,19 +211,19 @@ void TextLine::set_bidi_override(const Vector<Vector2i> &p_override) { 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) { 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); - spacing_top = p_fonts->get_spacing(Font::SPACING_TOP); - spacing_bottom = p_fonts->get_spacing(Font::SPACING_BOTTOM); + spacing_top = p_fonts->get_spacing(TextServer::SPACING_TOP); + spacing_bottom = p_fonts->get_spacing(TextServer::SPACING_BOTTOM); dirty = true; return res; } -bool TextLine::add_object(Variant p_key, const Size2 &p_size, VAlign p_inline_align, int p_length) { +bool TextLine::add_object(Variant p_key, const Size2 &p_size, InlineAlign p_inline_align, int p_length) { bool res = TS->shaped_text_add_object(rid, p_key, p_size, p_inline_align, p_length); dirty = true; return res; } -bool TextLine::resize_object(Variant p_key, const Size2 &p_size, VAlign p_inline_align) { +bool TextLine::resize_object(Variant p_key, const Size2 &p_size, InlineAlign p_inline_align) { const_cast<TextLine *>(this)->_shape(); return TS->shaped_text_resize_object(rid, p_key, p_size, p_inline_align); } @@ -225,9 +267,20 @@ uint8_t TextLine::get_flags() const { return flags; } +void TextLine::set_text_overrun_behavior(TextLine::OverrunBehavior p_behavior) { + if (overrun_behavior != p_behavior) { + overrun_behavior = p_behavior; + dirty = true; + } +} + +TextLine::OverrunBehavior TextLine::get_text_overrun_behavior() const { + return overrun_behavior; +} + void TextLine::set_width(float p_width) { width = p_width; - if (align == HALIGN_FILL) { + if (align == HALIGN_FILL || overrun_behavior != OVERRUN_NO_TRIMMING) { dirty = true; } } @@ -356,8 +409,8 @@ int TextLine::hit_test(float p_coords) const { 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) { rid = TS->create_shaped_text(p_direction, p_orientation); - spacing_top = p_fonts->get_spacing(Font::SPACING_TOP); - spacing_bottom = p_fonts->get_spacing(Font::SPACING_BOTTOM); + 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); } diff --git a/scene/resources/text_line.h b/scene/resources/text_line.h index 1b5c1a3123..9ed9c2f177 100644 --- a/scene/resources/text_line.h +++ b/scene/resources/text_line.h @@ -39,6 +39,16 @@ 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; @@ -48,6 +58,7 @@ class TextLine : public RefCounted { float width = -1.0; uint8_t flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA; HAlign align = HALIGN_LEFT; + OverrunBehavior overrun_behavior = OVERRUN_TRIM_ELLIPSIS; Vector<float> tab_stops; @@ -76,8 +87,8 @@ public: 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 = ""); - bool add_object(Variant p_key, const Size2 &p_size, VAlign p_inline_align = VALIGN_CENTER, int p_length = 1); - bool resize_object(Variant p_key, const Size2 &p_size, VAlign p_inline_align = VALIGN_CENTER); + bool add_object(Variant p_key, const Size2 &p_size, InlineAlign p_inline_align = INLINE_ALIGN_CENTER, int p_length = 1); + bool resize_object(Variant p_key, const Size2 &p_size, InlineAlign p_inline_align = INLINE_ALIGN_CENTER); void set_align(HAlign p_align); HAlign get_align() const; @@ -87,6 +98,9 @@ public: void set_flags(uint8_t p_flags); uint8_t get_flags() const; + void set_text_overrun_behavior(OverrunBehavior p_behavior); + OverrunBehavior get_text_overrun_behavior() const; + void set_width(float p_width); float get_width() const; @@ -113,4 +127,6 @@ public: ~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 958c94fe31..62949b9b98 100644 --- a/scene/resources/text_paragraph.cpp +++ b/scene/resources/text_paragraph.cpp @@ -59,8 +59,8 @@ void TextParagraph::_bind_methods() { ClassDB::bind_method(D_METHOD("clear_dropcap"), &TextParagraph::clear_dropcap); ClassDB::bind_method(D_METHOD("add_string", "text", "fonts", "size", "opentype_features", "language"), &TextParagraph::add_string, DEFVAL(Dictionary()), DEFVAL("")); - ClassDB::bind_method(D_METHOD("add_object", "key", "size", "inline_align", "length"), &TextParagraph::add_object, DEFVAL(VALIGN_CENTER), DEFVAL(1)); - ClassDB::bind_method(D_METHOD("resize_object", "key", "size", "inline_align"), &TextParagraph::resize_object, DEFVAL(VALIGN_CENTER)); + ClassDB::bind_method(D_METHOD("add_object", "key", "size", "inline_align", "length"), &TextParagraph::add_object, DEFVAL(INLINE_ALIGN_CENTER), DEFVAL(1)); + ClassDB::bind_method(D_METHOD("resize_object", "key", "size", "inline_align"), &TextParagraph::resize_object, DEFVAL(INLINE_ALIGN_CENTER)); ClassDB::bind_method(D_METHOD("set_align", "align"), &TextParagraph::set_align); ClassDB::bind_method(D_METHOD("get_align"), &TextParagraph::get_align); @@ -74,6 +74,11 @@ void TextParagraph::_bind_methods() { 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"); + 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); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior"); + ClassDB::bind_method(D_METHOD("set_width", "width"), &TextParagraph::set_width); ClassDB::bind_method(D_METHOD("get_width"), &TextParagraph::get_width); @@ -88,6 +93,11 @@ void TextParagraph::_bind_methods() { ClassDB::bind_method(D_METHOD("get_line_count"), &TextParagraph::get_line_count); + ClassDB::bind_method(D_METHOD("set_max_lines_visible", "max_lines_visible"), &TextParagraph::set_max_lines_visible); + ClassDB::bind_method(D_METHOD("get_max_lines_visible"), &TextParagraph::get_max_lines_visible); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_lines_visible"), "set_max_lines_visible", "get_max_lines_visible"); + ClassDB::bind_method(D_METHOD("get_line_objects", "line"), &TextParagraph::get_line_objects); ClassDB::bind_method(D_METHOD("get_line_object_rect", "line", "key"), &TextParagraph::get_line_object_rect); ClassDB::bind_method(D_METHOD("get_line_size", "line"), &TextParagraph::get_line_size); @@ -114,14 +124,20 @@ 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 (dirty_lines) { - for (int i = 0; i < lines.size(); i++) { - TS->free(lines[i]); + if (lines_dirty) { + for (int i = 0; i < lines_rid.size(); i++) { + TS->free(lines_rid[i]); } - lines.clear(); + lines_rid.clear(); if (!tab_stops.is_empty()) { TS->shaped_text_tab_align(rid, tab_stops); @@ -153,13 +169,10 @@ void TextParagraph::_shape_lines() { if (!tab_stops.is_empty()) { TS->shaped_text_tab_align(line, tab_stops); } - if (align == HALIGN_FILL && (line_breaks.size() == 1 || i < line_breaks.size() - 1)) { - TS->shaped_text_fit_to_width(line, width - h_offset, flags); - } dropcap_lines++; v_offset -= h; start = line_breaks[i].y; - lines.push_back(line); + lines_rid.push_back(line); } } // Use fixed for the rest of lines. @@ -169,12 +182,69 @@ void TextParagraph::_shape_lines() { if (!tab_stops.is_empty()) { TS->shaped_text_tab_align(line, tab_stops); } - if (align == HALIGN_FILL && (line_breaks.size() == 1 || i < line_breaks.size() - 1)) { - TS->shaped_text_fit_to_width(line, width, flags); + lines_rid.push_back(line); + } + + uint8_t overrun_flags = TextServer::OVERRUN_NO_TRIMMING; + if (overrun_behavior != 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; + break; + case OVERRUN_TRIM_ELLIPSIS: + overrun_flags |= TextServer::OVERRUN_TRIM; + overrun_flags |= TextServer::OVERRUN_ADD_ELLIPSIS; + break; + case OVERRUN_TRIM_WORD: + overrun_flags |= TextServer::OVERRUN_TRIM; + overrun_flags |= TextServer::OVERRUN_TRIM_WORD_ONLY; + break; + case OVERRUN_TRIM_CHAR: + overrun_flags |= TextServer::OVERRUN_TRIM; + break; + case 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); + + // 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(); + if (lines_hidden) { + overrun_flags |= TextServer::OVERRUN_ENFORCE_ELLIPSIS; + } + if (align == HALIGN_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); + } else if (i == (visible_lines - 1)) { + TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], width, overrun_flags); + } + } + + } else if (lines_hidden) { + TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], width, overrun_flags); + } + + } else { + // Autowrap disabled. + for (int i = 0; i < lines_rid.size(); i++) { + if (align == HALIGN_FILL) { + TS->shaped_text_fit_to_width(lines_rid[i], width, flags); + overrun_flags |= 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); + } else { + TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags); + } } - lines.push_back(line); } - dirty_lines = false; + lines_dirty = false; } } @@ -184,8 +254,8 @@ RID TextParagraph::get_rid() const { RID TextParagraph::get_line_rid(int p_line) const { const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines.size(), RID()); - return lines[p_line]; + ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), RID()); + return lines_rid[p_line]; } RID TextParagraph::get_dropcap_rid() const { @@ -195,10 +265,10 @@ RID TextParagraph::get_dropcap_rid() const { void TextParagraph::clear() { spacing_top = 0; spacing_bottom = 0; - for (int i = 0; i < lines.size(); i++) { - TS->free(lines[i]); + for (int i = 0; i < lines_rid.size(); i++) { + TS->free(lines_rid[i]); } - lines.clear(); + lines_rid.clear(); TS->shaped_text_clear(rid); TS->shaped_text_clear(dropcap_rid); } @@ -206,7 +276,7 @@ void TextParagraph::clear() { void TextParagraph::set_preserve_invalid(bool p_enabled) { TS->shaped_text_set_preserve_invalid(rid, p_enabled); TS->shaped_text_set_preserve_invalid(dropcap_rid, p_enabled); - dirty_lines = true; + lines_dirty = true; } bool TextParagraph::get_preserve_invalid() const { @@ -216,7 +286,7 @@ bool TextParagraph::get_preserve_invalid() const { void TextParagraph::set_preserve_control(bool p_enabled) { TS->shaped_text_set_preserve_control(rid, p_enabled); TS->shaped_text_set_preserve_control(dropcap_rid, p_enabled); - dirty_lines = true; + lines_dirty = true; } bool TextParagraph::get_preserve_control() const { @@ -226,7 +296,7 @@ bool TextParagraph::get_preserve_control() const { void TextParagraph::set_direction(TextServer::Direction p_direction) { TS->shaped_text_set_direction(rid, p_direction); TS->shaped_text_set_direction(dropcap_rid, p_direction); - dirty_lines = true; + lines_dirty = true; } TextServer::Direction TextParagraph::get_direction() const { @@ -237,7 +307,7 @@ TextServer::Direction TextParagraph::get_direction() const { void TextParagraph::set_orientation(TextServer::Orientation p_orientation) { TS->shaped_text_set_orientation(rid, p_orientation); TS->shaped_text_set_orientation(dropcap_rid, p_orientation); - dirty_lines = true; + lines_dirty = true; } TextServer::Orientation TextParagraph::get_orientation() const { @@ -250,22 +320,22 @@ bool TextParagraph::set_dropcap(const String &p_text, const Ref<Font> &p_fonts, 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); - dirty_lines = true; + lines_dirty = true; return res; } void TextParagraph::clear_dropcap() { dropcap_margins = Rect2(); TS->shaped_text_clear(dropcap_rid); - dirty_lines = true; + 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) { 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); - spacing_top = p_fonts->get_spacing(Font::SPACING_TOP); - spacing_bottom = p_fonts->get_spacing(Font::SPACING_BOTTOM); - dirty_lines = true; + spacing_top = p_fonts->get_spacing(TextServer::SPACING_TOP); + spacing_bottom = p_fonts->get_spacing(TextServer::SPACING_BOTTOM); + lines_dirty = true; return res; } @@ -287,18 +357,18 @@ void TextParagraph::_set_bidi_override(const Array &p_override) { void TextParagraph::set_bidi_override(const Vector<Vector2i> &p_override) { TS->shaped_text_set_bidi_override(rid, p_override); - dirty_lines = true; + lines_dirty = true; } -bool TextParagraph::add_object(Variant p_key, const Size2 &p_size, VAlign p_inline_align, int p_length) { +bool TextParagraph::add_object(Variant p_key, const Size2 &p_size, InlineAlign p_inline_align, int p_length) { bool res = TS->shaped_text_add_object(rid, p_key, p_size, p_inline_align, p_length); - dirty_lines = true; + lines_dirty = true; return res; } -bool TextParagraph::resize_object(Variant p_key, const Size2 &p_size, VAlign p_inline_align) { +bool TextParagraph::resize_object(Variant p_key, const Size2 &p_size, InlineAlign p_inline_align) { bool res = TS->shaped_text_resize_object(rid, p_key, p_size, p_inline_align); - dirty_lines = true; + lines_dirty = true; return res; } @@ -306,7 +376,7 @@ void TextParagraph::set_align(HAlign p_align) { if (align != p_align) { if (align == HALIGN_FILL || p_align == HALIGN_FILL) { align = p_align; - dirty_lines = true; + lines_dirty = true; } else { align = p_align; } @@ -319,13 +389,13 @@ HAlign TextParagraph::get_align() const { void TextParagraph::tab_align(const Vector<float> &p_tab_stops) { tab_stops = p_tab_stops; - dirty_lines = true; + lines_dirty = true; } void TextParagraph::set_flags(uint8_t p_flags) { if (flags != p_flags) { flags = p_flags; - dirty_lines = true; + lines_dirty = true; } } @@ -333,10 +403,21 @@ uint8_t TextParagraph::get_flags() const { return flags; } +void TextParagraph::set_text_overrun_behavior(TextParagraph::OverrunBehavior p_behavior) { + if (overrun_behavior != p_behavior) { + overrun_behavior = p_behavior; + lines_dirty = true; + } +} + +TextParagraph::OverrunBehavior TextParagraph::get_text_overrun_behavior() const { + return overrun_behavior; +} + void TextParagraph::set_width(float p_width) { if (width != p_width) { width = p_width; - dirty_lines = true; + lines_dirty = true; } } @@ -356,9 +437,10 @@ Size2 TextParagraph::get_non_wraped_size() const { Size2 TextParagraph::get_size() const { const_cast<TextParagraph *>(this)->_shape_lines(); Size2 size; - for (int i = 0; i < lines.size(); i++) { - Size2 lsize = TS->shaped_text_get_size(lines[i]); - if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { + int visible_lines = (max_lines_visible >= 0) ? MIN(max_lines_visible, lines_rid.size()) : 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; } else { @@ -371,22 +453,33 @@ Size2 TextParagraph::get_size() const { int TextParagraph::get_line_count() const { const_cast<TextParagraph *>(this)->_shape_lines(); - return lines.size(); + return lines_rid.size(); +} + +void TextParagraph::set_max_lines_visible(int p_lines) { + if (p_lines != max_lines_visible) { + max_lines_visible = p_lines; + lines_dirty = true; + } +} + +int TextParagraph::get_max_lines_visible() const { + return max_lines_visible; } Array TextParagraph::get_line_objects(int p_line) const { const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines.size(), Array()); - return TS->shaped_text_get_objects(lines[p_line]); + ERR_FAIL_COND_V(p_line < 0 || p_line >= 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 { const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines.size(), Rect2()); - Rect2 xrect = TS->shaped_text_get_object_rect(lines[p_line], p_key); + ERR_FAIL_COND_V(p_line < 0 || p_line >= 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[i]); - if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { + 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; } else { xrect.position.x += lsize.x + spacing_top + spacing_bottom; @@ -397,48 +490,48 @@ Rect2 TextParagraph::get_line_object_rect(int p_line, Variant p_key) const { Size2 TextParagraph::get_line_size(int p_line) const { const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines.size(), Size2()); - if (TS->shaped_text_get_orientation(lines[p_line]) == TextServer::ORIENTATION_HORIZONTAL) { - return Size2(TS->shaped_text_get_size(lines[p_line]).x, TS->shaped_text_get_size(lines[p_line]).y + spacing_top + spacing_bottom); + ERR_FAIL_COND_V(p_line < 0 || p_line >= 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); } else { - return Size2(TS->shaped_text_get_size(lines[p_line]).x + spacing_top + spacing_bottom, TS->shaped_text_get_size(lines[p_line]).y); + 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); } } Vector2i TextParagraph::get_line_range(int p_line) const { const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines.size(), Vector2i()); - return TS->shaped_text_get_range(lines[p_line]); + ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), Vector2i()); + return TS->shaped_text_get_range(lines_rid[p_line]); } float TextParagraph::get_line_ascent(int p_line) const { const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines.size(), 0.f); - return TS->shaped_text_get_ascent(lines[p_line]) + spacing_top; + 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; } float TextParagraph::get_line_descent(int p_line) const { const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines.size(), 0.f); - return TS->shaped_text_get_descent(lines[p_line]) + spacing_bottom; + 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; } float TextParagraph::get_line_width(int p_line) const { const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines.size(), 0.f); - return TS->shaped_text_get_width(lines[p_line]); + ERR_FAIL_COND_V(p_line < 0 || p_line >= 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 { const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines.size(), 0.f); - return TS->shaped_text_get_underline_position(lines[p_line]); + ERR_FAIL_COND_V(p_line < 0 || p_line >= 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 { const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines.size(), 0.f); - return TS->shaped_text_get_underline_thickness(lines[p_line]); + ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), 0.f); + return TS->shaped_text_get_underline_thickness(lines_rid[p_line]); } Size2 TextParagraph::get_dropcap_size() const { @@ -472,11 +565,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); } - for (int i = 0; i < lines.size(); i++) { + int lines_visible = (max_lines_visible >= 0) ? MIN(max_lines_visible, lines_rid.size()) : lines_rid.size(); + + for (int i = 0; i < lines_visible; i++) { float l_width = width; - if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { + 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[i]) + spacing_top; + ofs.y += TS->shaped_text_get_ascent(lines_rid[i]) + spacing_top; if (i <= dropcap_lines) { if (TS->shaped_text_get_direction(dropcap_rid) == TextServer::DIRECTION_LTR) { ofs.x -= h_offset; @@ -485,7 +580,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[i]) + spacing_top; + ofs.x += TS->shaped_text_get_ascent(lines_rid[i]) + spacing_top; if (i <= dropcap_lines) { if (TS->shaped_text_get_direction(dropcap_rid) == TextServer::DIRECTION_LTR) { ofs.x -= h_offset; @@ -493,41 +588,49 @@ void TextParagraph::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_colo l_width -= h_offset; } } - float length = TS->shaped_text_get_width(lines[i]); + float line_width = TS->shaped_text_get_width(lines_rid[i]); if (width > 0) { switch (align) { case HALIGN_FILL: + if (TS->shaped_text_get_direction(lines_rid[i]) == TextServer::DIRECTION_RTL) { + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { + ofs.x += l_width - line_width; + } else { + ofs.y += l_width - line_width; + } + } + break; case HALIGN_LEFT: break; case HALIGN_CENTER: { - if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { - ofs.x += Math::floor((l_width - length) / 2.0); + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { + ofs.x += Math::floor((l_width - line_width) / 2.0); } else { - ofs.y += Math::floor((l_width - length) / 2.0); + ofs.y += Math::floor((l_width - line_width) / 2.0); } } break; case HALIGN_RIGHT: { - if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { - ofs.x += l_width - length; + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { + ofs.x += l_width - line_width; } else { - ofs.y += l_width - length; + ofs.y += l_width - line_width; } } break; } } float clip_l; - if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { clip_l = MAX(0, p_pos.x - ofs.x); } else { clip_l = MAX(0, p_pos.y - ofs.y); } - TS->shaped_text_draw(lines[i], p_canvas, ofs, clip_l, clip_l + l_width, p_color); - if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { + 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[i]) + spacing_bottom; + ofs.y += TS->shaped_text_get_descent(lines_rid[i]) + spacing_bottom; } else { ofs.y = p_pos.y; - ofs.x += TS->shaped_text_get_descent(lines[i]) + spacing_bottom; + ofs.x += TS->shaped_text_get_descent(lines_rid[i]) + spacing_bottom; } } } @@ -556,11 +659,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.size(); i++) { + for (int i = 0; i < lines_rid.size(); i++) { float l_width = width; - if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { + 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[i]) + spacing_top; + ofs.y += TS->shaped_text_get_ascent(lines_rid[i]) + spacing_top; if (i <= dropcap_lines) { if (TS->shaped_text_get_direction(dropcap_rid) == TextServer::DIRECTION_LTR) { ofs.x -= h_offset; @@ -569,7 +672,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[i]) + spacing_top; + ofs.x += TS->shaped_text_get_ascent(lines_rid[i]) + spacing_top; if (i <= dropcap_lines) { if (TS->shaped_text_get_direction(dropcap_rid) == TextServer::DIRECTION_LTR) { ofs.x -= h_offset; @@ -577,21 +680,29 @@ void TextParagraph::draw_outline(RID p_canvas, const Vector2 &p_pos, int p_outli l_width -= h_offset; } } - float length = TS->shaped_text_get_width(lines[i]); + float length = TS->shaped_text_get_width(lines_rid[i]); if (width > 0) { switch (align) { case HALIGN_FILL: + if (TS->shaped_text_get_direction(lines_rid[i]) == TextServer::DIRECTION_RTL) { + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { + ofs.x += l_width - length; + } else { + ofs.y += l_width - length; + } + } + break; case HALIGN_LEFT: break; case HALIGN_CENTER: { - if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { ofs.x += Math::floor((l_width - length) / 2.0); } else { ofs.y += Math::floor((l_width - length) / 2.0); } } break; case HALIGN_RIGHT: { - if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { ofs.x += l_width - length; } else { ofs.y += l_width - length; @@ -600,18 +711,18 @@ void TextParagraph::draw_outline(RID p_canvas, const Vector2 &p_pos, int p_outli } } float clip_l; - if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { clip_l = MAX(0, p_pos.x - ofs.x); } else { clip_l = MAX(0, p_pos.y - ofs.y); } - TS->shaped_text_draw_outline(lines[i], p_canvas, ofs, clip_l, clip_l + l_width, p_outline_size, p_color); - if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { + 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[i]) + spacing_bottom; + ofs.y += TS->shaped_text_get_descent(lines_rid[i]) + spacing_bottom; } else { ofs.y = p_pos.y; - ofs.x += TS->shaped_text_get_descent(lines[i]) + spacing_bottom; + ofs.x += TS->shaped_text_get_descent(lines_rid[i]) + spacing_bottom; } } } @@ -628,17 +739,17 @@ int TextParagraph::hit_test(const Point2 &p_coords) const { return 0; } } - for (int i = 0; i < lines.size(); i++) { - if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { - if ((p_coords.y >= ofs.y) && (p_coords.y <= ofs.y + TS->shaped_text_get_size(lines[i]).y)) { - return TS->shaped_text_hit_test_position(lines[i], p_coords.x); + for (int i = 0; i < 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[i]).y + spacing_bottom + spacing_top; + ofs.y += TS->shaped_text_get_size(lines_rid[i]).y + spacing_bottom + spacing_top; } else { - if ((p_coords.x >= ofs.x) && (p_coords.x <= ofs.x + TS->shaped_text_get_size(lines[i]).x)) { - return TS->shaped_text_hit_test_position(lines[i], p_coords.y); + 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[i]).x + spacing_bottom + spacing_top; + ofs.y += TS->shaped_text_get_size(lines_rid[i]).x + spacing_bottom + spacing_top; } } return TS->shaped_text_get_range(rid).y; @@ -690,36 +801,36 @@ 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 { const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND(p_line < 0 || p_line >= lines.size()); + ERR_FAIL_COND(p_line < 0 || p_line >= lines_rid.size()); Vector2 ofs = p_pos; - if (TS->shaped_text_get_orientation(lines[p_line]) == TextServer::ORIENTATION_HORIZONTAL) { - ofs.y += TS->shaped_text_get_ascent(lines[p_line]) + spacing_top; + 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; } else { - ofs.x += TS->shaped_text_get_ascent(lines[p_line]) + spacing_top; + ofs.x += TS->shaped_text_get_ascent(lines_rid[p_line]) + spacing_top; } - return TS->shaped_text_draw(lines[p_line], p_canvas, ofs, -1, -1, p_color); + 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 { const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND(p_line < 0 || p_line >= lines.size()); + ERR_FAIL_COND(p_line < 0 || p_line >= lines_rid.size()); Vector2 ofs = p_pos; - if (TS->shaped_text_get_orientation(lines[p_line]) == TextServer::ORIENTATION_HORIZONTAL) { - ofs.y += TS->shaped_text_get_ascent(lines[p_line]) + spacing_top; + 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; } else { - ofs.x += TS->shaped_text_get_ascent(lines[p_line]) + spacing_top; + ofs.x += TS->shaped_text_get_ascent(lines_rid[p_line]) + spacing_top; } - return TS->shaped_text_draw_outline(lines[p_line], p_canvas, ofs, -1, -1, p_outline_size, p_color); + 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) { 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(Font::SPACING_TOP); - spacing_bottom = p_fonts->get_spacing(Font::SPACING_BOTTOM); + spacing_top = p_fonts->get_spacing(TextServer::SPACING_TOP); + spacing_bottom = p_fonts->get_spacing(TextServer::SPACING_BOTTOM); width = p_width; } @@ -729,10 +840,10 @@ TextParagraph::TextParagraph() { } TextParagraph::~TextParagraph() { - for (int i = 0; i < lines.size(); i++) { - TS->free(lines[i]); + for (int i = 0; i < lines_rid.size(); i++) { + TS->free(lines_rid[i]); } - lines.clear(); + lines_rid.clear(); TS->free(rid); TS->free(dropcap_rid); } diff --git a/scene/resources/text_paragraph.h b/scene/resources/text_paragraph.h index a34e745090..ee7bbab9c5 100644 --- a/scene/resources/text_paragraph.h +++ b/scene/resources/text_paragraph.h @@ -39,19 +39,33 @@ 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, + }; + +private: RID dropcap_rid; int dropcap_lines = 0; Rect2 dropcap_margins; RID rid; - Vector<RID> lines; + Vector<RID> lines_rid; int spacing_top = 0; int spacing_bottom = 0; - bool dirty_lines = true; + bool lines_dirty = true; float width = -1.0; + int max_lines_visible = -1; + uint8_t flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA; + OverrunBehavior overrun_behavior = OVERRUN_NO_TRIMMING; + HAlign align = HALIGN_LEFT; Vector<float> tab_stops; @@ -86,8 +100,8 @@ public: 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 = ""); - bool add_object(Variant p_key, const Size2 &p_size, VAlign p_inline_align = VALIGN_CENTER, int p_length = 1); - bool resize_object(Variant p_key, const Size2 &p_size, VAlign p_inline_align = VALIGN_CENTER); + bool add_object(Variant p_key, const Size2 &p_size, InlineAlign p_inline_align = INLINE_ALIGN_CENTER, int p_length = 1); + bool resize_object(Variant p_key, const Size2 &p_size, InlineAlign p_inline_align = INLINE_ALIGN_CENTER); void set_align(HAlign p_align); HAlign get_align() const; @@ -97,9 +111,15 @@ public: void set_flags(uint8_t p_flags); uint8_t get_flags() const; + void set_text_overrun_behavior(OverrunBehavior p_behavior); + OverrunBehavior get_text_overrun_behavior() const; + void set_width(float p_width); float get_width() const; + void set_max_lines_visible(int p_lines); + int get_max_lines_visible() const; + Size2 get_non_wraped_size() const; Size2 get_size() const; @@ -140,4 +160,6 @@ public: ~TextParagraph(); }; +VARIANT_ENUM_CAST(TextParagraph::OverrunBehavior); + #endif // TEXT_PARAGRAPH_H diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index d09a1d9f90..3dc32632cc 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -131,25 +131,6 @@ void ImageTexture::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::VECTOR2, "size", PROPERTY_HINT_NONE, "")); } -void ImageTexture::_reload_hook(const RID &p_hook) { - String path = get_path(); - if (!path.is_resource_file()) { - return; - } - - Ref<Image> img; - img.instantiate(); - Error err = ImageLoader::load_image(path, img); - - ERR_FAIL_COND_MSG(err != OK, "Cannot load image from path '" + path + "'."); - - RID new_texture = RenderingServer::get_singleton()->texture_2d_create(img); - RenderingServer::get_singleton()->texture_replace(texture, new_texture); - - notify_property_list_changed(); - emit_changed(); -} - void ImageTexture::create_from_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(); @@ -192,10 +173,6 @@ void ImageTexture::update(const Ref<Image> &p_image) { image_stored = true; } -void ImageTexture::_resource_path_changed() { - String path = get_path(); -} - Ref<Image> ImageTexture::get_image() const { if (image_stored) { return RenderingServer::get_singleton()->texture_2d_get(texture); @@ -303,7 +280,6 @@ void ImageTexture::_bind_methods() { ClassDB::bind_method(D_METHOD("update", "image"), &ImageTexture::update); ClassDB::bind_method(D_METHOD("set_size_override", "size"), &ImageTexture::set_size_override); - ClassDB::bind_method(D_METHOD("_reload_hook", "rid"), &ImageTexture::_reload_hook); } ImageTexture::ImageTexture() {} @@ -354,11 +330,11 @@ Ref<Image> StreamTexture2D::load_image_from_file(FileAccess *f, int p_size_limit } Ref<Image> img; - if (data_format == DATA_FORMAT_BASIS_UNIVERSAL) { + if (data_format == DATA_FORMAT_BASIS_UNIVERSAL && Image::basis_universal_unpacker) { img = Image::basis_universal_unpacker(pv); - } else if (data_format == DATA_FORMAT_PNG) { + } else if (data_format == DATA_FORMAT_PNG && Image::png_unpacker) { img = Image::png_unpacker(pv); - } else { + } else if (data_format == DATA_FORMAT_WEBP && Image::webp_unpacker) { img = Image::webp_unpacker(pv); } @@ -1412,7 +1388,7 @@ void CurveTexture::_bind_methods() { ClassDB::bind_method(D_METHOD("_update"), &CurveTexture::_update); - ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "32,4096"), "set_width", "get_width"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,4096"), "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"); @@ -1545,27 +1521,27 @@ CurveTexture::~CurveTexture() { ////////////////// -void Curve3Texture::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_width", "width"), &Curve3Texture::set_width); +void CurveXYZTexture::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_width", "width"), &CurveXYZTexture::set_width); - ClassDB::bind_method(D_METHOD("set_curve_x", "curve"), &Curve3Texture::set_curve_x); - ClassDB::bind_method(D_METHOD("get_curve_x"), &Curve3Texture::get_curve_x); + ClassDB::bind_method(D_METHOD("set_curve_x", "curve"), &CurveXYZTexture::set_curve_x); + ClassDB::bind_method(D_METHOD("get_curve_x"), &CurveXYZTexture::get_curve_x); - ClassDB::bind_method(D_METHOD("set_curve_y", "curve"), &Curve3Texture::set_curve_y); - ClassDB::bind_method(D_METHOD("get_curve_y"), &Curve3Texture::get_curve_y); + ClassDB::bind_method(D_METHOD("set_curve_y", "curve"), &CurveXYZTexture::set_curve_y); + ClassDB::bind_method(D_METHOD("get_curve_y"), &CurveXYZTexture::get_curve_y); - ClassDB::bind_method(D_METHOD("set_curve_z", "curve"), &Curve3Texture::set_curve_z); - ClassDB::bind_method(D_METHOD("get_curve_z"), &Curve3Texture::get_curve_z); + ClassDB::bind_method(D_METHOD("set_curve_z", "curve"), &CurveXYZTexture::set_curve_z); + ClassDB::bind_method(D_METHOD("get_curve_z"), &CurveXYZTexture::get_curve_z); - ClassDB::bind_method(D_METHOD("_update"), &Curve3Texture::_update); + ClassDB::bind_method(D_METHOD("_update"), &CurveXYZTexture::_update); - ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "32,4096"), "set_width", "get_width"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,4096"), "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"); } -void Curve3Texture::set_width(int p_width) { +void CurveXYZTexture::set_width(int p_width) { ERR_FAIL_COND(p_width < 32 || p_width > 4096); if (_width == p_width) { @@ -1576,11 +1552,11 @@ void Curve3Texture::set_width(int p_width) { _update(); } -int Curve3Texture::get_width() const { +int CurveXYZTexture::get_width() const { return _width; } -void Curve3Texture::ensure_default_setup(float p_min, float p_max) { +void CurveXYZTexture::ensure_default_setup(float p_min, float p_max) { if (_curve_x.is_null()) { Ref<Curve> curve = Ref<Curve>(memnew(Curve)); curve->add_point(Vector2(0, 1)); @@ -1609,46 +1585,46 @@ void Curve3Texture::ensure_default_setup(float p_min, float p_max) { } } -void Curve3Texture::set_curve_x(Ref<Curve> p_curve) { +void CurveXYZTexture::set_curve_x(Ref<Curve> p_curve) { if (_curve_x != p_curve) { if (_curve_x.is_valid()) { - _curve_x->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Curve3Texture::_update)); + _curve_x->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveXYZTexture::_update)); } _curve_x = p_curve; if (_curve_x.is_valid()) { - _curve_x->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Curve3Texture::_update), varray(), CONNECT_REFERENCE_COUNTED); + _curve_x->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveXYZTexture::_update), varray(), CONNECT_REFERENCE_COUNTED); } _update(); } } -void Curve3Texture::set_curve_y(Ref<Curve> p_curve) { +void CurveXYZTexture::set_curve_y(Ref<Curve> p_curve) { if (_curve_y != p_curve) { if (_curve_y.is_valid()) { - _curve_y->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Curve3Texture::_update)); + _curve_y->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveXYZTexture::_update)); } _curve_y = p_curve; if (_curve_y.is_valid()) { - _curve_y->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Curve3Texture::_update), varray(), CONNECT_REFERENCE_COUNTED); + _curve_y->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveXYZTexture::_update), varray(), CONNECT_REFERENCE_COUNTED); } _update(); } } -void Curve3Texture::set_curve_z(Ref<Curve> p_curve) { +void CurveXYZTexture::set_curve_z(Ref<Curve> p_curve) { if (_curve_z != p_curve) { if (_curve_z.is_valid()) { - _curve_z->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Curve3Texture::_update)); + _curve_z->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveXYZTexture::_update)); } _curve_z = p_curve; if (_curve_z.is_valid()) { - _curve_z->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Curve3Texture::_update), varray(), CONNECT_REFERENCE_COUNTED); + _curve_z->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CurveXYZTexture::_update), varray(), CONNECT_REFERENCE_COUNTED); } _update(); } } -void Curve3Texture::_update() { +void CurveXYZTexture::_update() { Vector<uint8_t> data; data.resize(_width * sizeof(float) * 3); @@ -1714,28 +1690,28 @@ void Curve3Texture::_update() { emit_changed(); } -Ref<Curve> Curve3Texture::get_curve_x() const { +Ref<Curve> CurveXYZTexture::get_curve_x() const { return _curve_x; } -Ref<Curve> Curve3Texture::get_curve_y() const { +Ref<Curve> CurveXYZTexture::get_curve_y() const { return _curve_y; } -Ref<Curve> Curve3Texture::get_curve_z() const { +Ref<Curve> CurveXYZTexture::get_curve_z() const { return _curve_z; } -RID Curve3Texture::get_rid() const { +RID CurveXYZTexture::get_rid() const { if (!_texture.is_valid()) { _texture = RS::get_singleton()->texture_2d_placeholder_create(); } return _texture; } -Curve3Texture::Curve3Texture() {} +CurveXYZTexture::CurveXYZTexture() {} -Curve3Texture::~Curve3Texture() { +CurveXYZTexture::~CurveXYZTexture() { if (_texture.is_valid()) { RS::get_singleton()->free(_texture); } @@ -1758,11 +1734,16 @@ void GradientTexture::_bind_methods() { ClassDB::bind_method(D_METHOD("get_gradient"), &GradientTexture::get_gradient); ClassDB::bind_method(D_METHOD("set_width", "width"), &GradientTexture::set_width); + // The `get_width()` method is already exposed by the parent class Texture2D. + + ClassDB::bind_method(D_METHOD("set_use_hdr", "enabled"), &GradientTexture::set_use_hdr); + ClassDB::bind_method(D_METHOD("is_using_hdr"), &GradientTexture::is_using_hdr); ClassDB::bind_method(D_METHOD("_update"), &GradientTexture::_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,2048,1,or_greater"), "set_width", "get_width"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "width", PROPERTY_HINT_RANGE, "1,4096"), "set_width", "get_width"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_hdr"), "set_use_hdr", "is_using_hdr"); } void GradientTexture::set_gradient(Ref<Gradient> p_gradient) { @@ -1790,7 +1771,7 @@ void GradientTexture::_queue_update() { } update_pending = true; - call_deferred("_update"); + call_deferred(SNAME("_update")); } void GradientTexture::_update() { @@ -1800,30 +1781,49 @@ void GradientTexture::_update() { return; } - Vector<uint8_t> data; - data.resize(width * 4); - { - uint8_t *wd8 = data.ptrw(); + if (use_hdr) { + // High dynamic range. + Ref<Image> image = memnew(Image(width, 1, false, Image::FORMAT_RGBAF)); Gradient &g = **gradient; - + // `create()` isn't available for non-uint8_t data, so fill in the data manually. for (int i = 0; i < width; i++) { float ofs = float(i) / (width - 1); - Color color = g.get_color_at_offset(ofs); + image->set_pixel(i, 0, g.get_color_at_offset(ofs)); + } - wd8[i * 4 + 0] = uint8_t(CLAMP(color.r * 255.0, 0, 255)); - wd8[i * 4 + 1] = uint8_t(CLAMP(color.g * 255.0, 0, 255)); - wd8[i * 4 + 2] = uint8_t(CLAMP(color.b * 255.0, 0, 255)); - wd8[i * 4 + 3] = uint8_t(CLAMP(color.a * 255.0, 0, 255)); + if (texture.is_valid()) { + RID new_texture = RS::get_singleton()->texture_2d_create(image); + RS::get_singleton()->texture_replace(texture, new_texture); + } else { + texture = RS::get_singleton()->texture_2d_create(image); + } + } else { + // Low dynamic range. "Overbright" colors will be clamped. + Vector<uint8_t> data; + data.resize(width * 4); + { + uint8_t *wd8 = data.ptrw(); + Gradient &g = **gradient; + + for (int i = 0; i < width; i++) { + float ofs = float(i) / (width - 1); + Color color = g.get_color_at_offset(ofs); + + wd8[i * 4 + 0] = uint8_t(CLAMP(color.r * 255.0, 0, 255)); + wd8[i * 4 + 1] = uint8_t(CLAMP(color.g * 255.0, 0, 255)); + wd8[i * 4 + 2] = uint8_t(CLAMP(color.b * 255.0, 0, 255)); + wd8[i * 4 + 3] = uint8_t(CLAMP(color.a * 255.0, 0, 255)); + } } - } - Ref<Image> image = memnew(Image(width, 1, false, Image::FORMAT_RGBA8, data)); + Ref<Image> image = memnew(Image(width, 1, false, Image::FORMAT_RGBA8, data)); - if (texture.is_valid()) { - RID new_texture = RS::get_singleton()->texture_2d_create(image); - RS::get_singleton()->texture_replace(texture, new_texture); - } else { - texture = RS::get_singleton()->texture_2d_create(image); + if (texture.is_valid()) { + RID new_texture = RS::get_singleton()->texture_2d_create(image); + RS::get_singleton()->texture_replace(texture, new_texture); + } else { + texture = RS::get_singleton()->texture_2d_create(image); + } } emit_changed(); @@ -1839,6 +1839,19 @@ int GradientTexture::get_width() const { return width; } +void GradientTexture::set_use_hdr(bool p_enabled) { + if (p_enabled == use_hdr) { + return; + } + + use_hdr = p_enabled; + _queue_update(); +} + +bool GradientTexture::is_using_hdr() const { + return use_hdr; +} + Ref<Image> GradientTexture::get_image() const { if (!texture.is_valid()) { return Ref<Image>(); @@ -2587,7 +2600,10 @@ RID CameraTexture::get_rid() const { if (feed.is_valid()) { return feed->get_texture(which_feed); } else { - return RID(); + if (_texture.is_null()) { + _texture = RenderingServer::get_singleton()->texture_2d_placeholder_create(); + } + return _texture; } } @@ -2643,5 +2659,7 @@ bool CameraTexture::get_camera_active() const { CameraTexture::CameraTexture() {} CameraTexture::~CameraTexture() { - // nothing to do here yet + if (_texture.is_valid()) { + RenderingServer::get_singleton()->free(_texture); + } } diff --git a/scene/resources/texture.h b/scene/resources/texture.h index 73390039cb..93f4e2de5a 100644 --- a/scene/resources/texture.h +++ b/scene/resources/texture.h @@ -98,8 +98,6 @@ protected: bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List<PropertyInfo> *p_list) const; - void _reload_hook(const RID &p_hook); - virtual void _resource_path_changed() override; static void _bind_methods(); public: @@ -628,8 +626,8 @@ public: VARIANT_ENUM_CAST(CurveTexture::TextureMode) -class Curve3Texture : public Texture2D { - GDCLASS(Curve3Texture, Texture2D); +class CurveXYZTexture : public Texture2D { + GDCLASS(CurveXYZTexture, Texture2D); RES_BASE_EXTENSION("curvetex") private: @@ -665,8 +663,8 @@ public: virtual int get_height() const override { return 1; } virtual bool has_alpha() const override { return false; } - Curve3Texture(); - ~Curve3Texture(); + CurveXYZTexture(); + ~CurveXYZTexture(); }; class GradientTexture : public Texture2D { @@ -686,6 +684,7 @@ private: bool update_pending = false; RID texture; int width = 2048; + bool use_hdr = false; void _queue_update(); void _update(); @@ -700,6 +699,9 @@ public: void set_width(int p_width); int get_width() const override; + void set_use_hdr(bool p_enabled); + bool is_using_hdr() const; + virtual RID get_rid() const override { return texture; } virtual int get_height() const override { return 1; } virtual bool has_alpha() const override { return true; } @@ -812,6 +814,7 @@ class CameraTexture : public Texture2D { GDCLASS(CameraTexture, Texture2D); private: + mutable RID _texture; int camera_feed_id = 0; CameraServer::FeedImage which_feed = CameraServer::FEED_RGBA_IMAGE; diff --git a/scene/resources/theme.cpp b/scene/resources/theme.cpp index 89ac033207..e49d883ba4 100644 --- a/scene/resources/theme.cpp +++ b/scene/resources/theme.cpp @@ -263,6 +263,21 @@ Vector<String> Theme::_get_theme_item_type_list(DataType p_data_type) const { return Vector<String>(); } +Vector<String> Theme::_get_type_variation_list(const StringName &p_theme_type) const { + Vector<String> ilret; + List<StringName> il; + + get_type_variation_list(p_theme_type, &il); + ilret.resize(il.size()); + + int i = 0; + String *w = ilret.ptrw(); + for (List<StringName>::Element *E = il.front(); E; E = E->next(), i++) { + w[i] = E->get(); + } + return ilret; +} + Vector<String> Theme::_get_type_list() const { Vector<String> ilret; List<StringName> il; @@ -292,10 +307,14 @@ bool Theme::_set(const StringName &p_name, const Variant &p_value) { set_stylebox(name, theme_type, p_value); } else if (type == "fonts") { set_font(name, theme_type, p_value); + } else if (type == "font_sizes") { + set_font_size(name, theme_type, p_value); } else if (type == "colors") { set_color(name, theme_type, p_value); } else if (type == "constants") { set_constant(name, theme_type, p_value); + } else if (type == "base_type") { + set_type_variation(theme_type, p_value); } else { return false; } @@ -332,10 +351,14 @@ bool Theme::_get(const StringName &p_name, Variant &r_ret) const { } else { r_ret = get_font(name, theme_type); } + } else if (type == "font_sizes") { + r_ret = get_font_size(name, theme_type); } else if (type == "colors") { r_ret = get_color(name, theme_type); } else if (type == "constants") { r_ret = get_constant(name, theme_type); + } else if (type == "base_type") { + r_ret = get_type_variation_base(theme_type); } else { return false; } @@ -351,6 +374,14 @@ void Theme::_get_property_list(List<PropertyInfo> *p_list) const { const StringName *key = nullptr; + // Type variations. + while ((key = variation_map.next(key))) { + list.push_back(PropertyInfo(Variant::STRING_NAME, String() + *key + "/base_type")); + } + + key = nullptr; + + // Icons. while ((key = icon_map.next(key))) { const StringName *key2 = nullptr; @@ -361,6 +392,7 @@ void Theme::_get_property_list(List<PropertyInfo> *p_list) const { key = nullptr; + // Styles. while ((key = style_map.next(key))) { const StringName *key2 = nullptr; @@ -371,6 +403,7 @@ void Theme::_get_property_list(List<PropertyInfo> *p_list) const { key = nullptr; + // Fonts. while ((key = font_map.next(key))) { const StringName *key2 = nullptr; @@ -381,6 +414,18 @@ void Theme::_get_property_list(List<PropertyInfo> *p_list) const { 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)); + } + } + + key = nullptr; + + // Colors. while ((key = color_map.next(key))) { const StringName *key2 = nullptr; @@ -391,6 +436,7 @@ void Theme::_get_property_list(List<PropertyInfo> *p_list) const { key = nullptr; + // Constants. while ((key = constant_map.next(key))) { const StringName *key2 = nullptr; @@ -399,9 +445,10 @@ void Theme::_get_property_list(List<PropertyInfo> *p_list) const { } } + // Sort and store properties. list.sort(); - for (List<PropertyInfo>::Element *E = list.front(); E; E = E->next()) { - p_list->push_back(E->get()); + for (const PropertyInfo &E : list) { + p_list->push_back(E); } } @@ -1183,6 +1230,63 @@ void Theme::get_theme_item_type_list(DataType p_data_type, List<StringName> *p_l } } +void Theme::set_type_variation(const StringName &p_theme_type, const StringName &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."); + + if (variation_map.has(p_theme_type)) { + StringName old_base = variation_map[p_theme_type]; + variation_base_map[old_base].erase(p_theme_type); + } + + variation_map[p_theme_type] = p_base_type; + variation_base_map[p_base_type].push_back(p_theme_type); + + _emit_theme_changed(); +} + +bool Theme::is_type_variation(const StringName &p_theme_type, const StringName &p_base_type) const { + return (variation_map.has(p_theme_type) && variation_map[p_theme_type] == p_base_type); +} + +void Theme::clear_type_variation(const StringName &p_theme_type) { + ERR_FAIL_COND_MSG(!variation_map.has(p_theme_type), "Cannot clear the type variation '" + String(p_theme_type) + "' because it does not exist."); + + StringName base_type = variation_map[p_theme_type]; + variation_base_map[base_type].erase(p_theme_type); + variation_map.erase(p_theme_type); + + _emit_theme_changed(); +} + +StringName Theme::get_type_variation_base(const StringName &p_theme_type) const { + if (!variation_map.has(p_theme_type)) { + return StringName(); + } + + return variation_map[p_theme_type]; +} + +void Theme::get_type_variation_list(const StringName &p_base_type, List<StringName> *p_list) const { + ERR_FAIL_NULL(p_list); + + if (!variation_base_map.has(p_base_type)) { + return; + } + + for (const StringName &E : variation_base_map[p_base_type]) { + // Prevent infinite loops if variants were set to be cross-dependent (that's still invalid usage, but handling for stability sake). + if (p_list->find(E)) { + continue; + } + + p_list->push_back(E); + // Continue looking for sub-variations. + get_type_variation_list(E, p_list); + } +} + void Theme::_freeze_change_propagation() { no_change_propagation = true; } @@ -1236,46 +1340,46 @@ void Theme::clear() { icon_map.clear(); style_map.clear(); font_map.clear(); + font_size_map.clear(); color_map.clear(); constant_map.clear(); - _emit_theme_changed(); -} + variation_map.clear(); + variation_base_map.clear(); -void Theme::copy_default_theme() { - Ref<Theme> default_theme2 = get_default(); - copy_theme(default_theme2); + _emit_theme_changed(); } -void Theme::copy_theme(const Ref<Theme> &p_other) { +void Theme::merge_with(const Ref<Theme> &p_other) { if (p_other.is_null()) { - clear(); return; } _freeze_change_propagation(); - // These items need reconnecting, so add them normally. + // Colors. { const StringName *K = nullptr; - while ((K = p_other->icon_map.next(K))) { + while ((K = p_other->color_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]); + while ((L = p_other->color_map[*K].next(L))) { + set_color(*L, *K, p_other->color_map[*K][*L]); } } } + // Constants. { const StringName *K = nullptr; - while ((K = p_other->style_map.next(K))) { + while ((K = p_other->constant_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]); + while ((L = p_other->constant_map[*K].next(L))) { + set_constant(*L, *K, p_other->constant_map[*K][*L]); } } } + // Fonts. { const StringName *K = nullptr; while ((K = p_other->font_map.next(K))) { @@ -1286,10 +1390,46 @@ void Theme::copy_theme(const Ref<Theme> &p_other) { } } - // These items can be simply copied. - font_size_map = p_other->font_size_map; - color_map = p_other->color_map; - constant_map = p_other->constant_map; + // 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]); + } + } + } + + // 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]); + } + } + } + + // 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]); + } + } + } + + // Type variations. + { + const StringName *K = nullptr; + while ((K = p_other->variation_map.next(K))) { + set_type_variation(*K, p_other->variation_map[*K]); + } + } _unfreeze_and_propagate_changes(); } @@ -1300,30 +1440,42 @@ void Theme::get_type_list(List<StringName> *p_list) const { Set<StringName> types; const StringName *key = nullptr; + // Icons. while ((key = icon_map.next(key))) { types.insert(*key); } key = nullptr; + // StyleBoxes. while ((key = style_map.next(key))) { types.insert(*key); } key = nullptr; + // Fonts. while ((key = font_map.next(key))) { types.insert(*key); } key = nullptr; + // Font sizes. + while ((key = font_size_map.next(key))) { + types.insert(*key); + } + + key = nullptr; + + // Colors. while ((key = color_map.next(key))) { types.insert(*key); } key = nullptr; + // Constants. while ((key = constant_map.next(key))) { types.insert(*key); } @@ -1333,10 +1485,25 @@ void Theme::get_type_list(List<StringName> *p_list) const { } } -void Theme::get_type_dependencies(const StringName &p_theme_type, List<StringName> *p_list) { +void Theme::get_type_dependencies(const StringName &p_base_type, const StringName &p_type_variation, List<StringName> *p_list) { ERR_FAIL_NULL(p_list); - StringName class_name = p_theme_type; + // Build the dependency chain for type variations. + if (p_type_variation != StringName()) { + StringName variation_name = p_type_variation; + while (variation_name != StringName()) { + p_list->push_back(variation_name); + variation_name = get_type_variation_base(variation_name); + + // If we have reached the base type dependency, it's safe to stop (assuming no funny business was done to the Theme). + if (variation_name == p_base_type) { + break; + } + } + } + + // Continue building the chain using native class hierarchy. + StringName class_name = p_base_type; while (class_name != StringName()) { p_list->push_back(class_name); class_name = ClassDB::get_parent_class_nocheck(class_name); @@ -1346,6 +1513,7 @@ void Theme::get_type_dependencies(const StringName &p_theme_type, List<StringNam void Theme::reset_state() { clear(); } + void Theme::_bind_methods() { ClassDB::bind_method(D_METHOD("set_icon", "name", "theme_type", "texture"), &Theme::set_icon); ClassDB::bind_method(D_METHOD("get_icon", "name", "theme_type"), &Theme::get_icon); @@ -1395,8 +1563,6 @@ void Theme::_bind_methods() { ClassDB::bind_method(D_METHOD("get_constant_list", "theme_type"), &Theme::_get_constant_list); ClassDB::bind_method(D_METHOD("get_constant_type_list"), &Theme::_get_constant_type_list); - ClassDB::bind_method(D_METHOD("clear"), &Theme::clear); - ClassDB::bind_method(D_METHOD("set_default_font", "font"), &Theme::set_default_theme_font); ClassDB::bind_method(D_METHOD("get_default_font"), &Theme::get_default_theme_font); @@ -1411,10 +1577,16 @@ void Theme::_bind_methods() { ClassDB::bind_method(D_METHOD("get_theme_item_list", "data_type", "theme_type"), &Theme::_get_theme_item_list); ClassDB::bind_method(D_METHOD("get_theme_item_type_list", "data_type"), &Theme::_get_theme_item_type_list); + ClassDB::bind_method(D_METHOD("set_type_variation", "theme_type", "base_type"), &Theme::set_type_variation); + ClassDB::bind_method(D_METHOD("is_type_variation", "theme_type", "base_type"), &Theme::is_type_variation); + ClassDB::bind_method(D_METHOD("clear_type_variation", "theme_type"), &Theme::clear_type_variation); + ClassDB::bind_method(D_METHOD("get_type_variation_base", "theme_type"), &Theme::get_type_variation_base); + ClassDB::bind_method(D_METHOD("get_type_variation_list", "base_type"), &Theme::_get_type_variation_list); + ClassDB::bind_method(D_METHOD("get_type_list"), &Theme::_get_type_list); - ClassDB::bind_method("copy_default_theme", &Theme::copy_default_theme); - ClassDB::bind_method(D_METHOD("copy_theme", "other"), &Theme::copy_theme); + ClassDB::bind_method(D_METHOD("merge_with", "other"), &Theme::merge_with); + ClassDB::bind_method(D_METHOD("clear"), &Theme::clear); 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"), "set_default_font_size", "get_default_font_size"); diff --git a/scene/resources/theme.h b/scene/resources/theme.h index fe64fd7290..15f21b91b8 100644 --- a/scene/resources/theme.h +++ b/scene/resources/theme.h @@ -69,6 +69,8 @@ private: HashMap<StringName, HashMap<StringName, int>> font_size_map; HashMap<StringName, HashMap<StringName, Color>> color_map; HashMap<StringName, HashMap<StringName, int>> constant_map; + HashMap<StringName, StringName> variation_map; + HashMap<StringName, List<StringName>> variation_base_map; Vector<String> _get_icon_list(const String &p_theme_type) const; Vector<String> _get_icon_type_list() const; @@ -85,6 +87,8 @@ private: Vector<String> _get_theme_item_list(DataType p_data_type, const String &p_theme_type) const; Vector<String> _get_theme_item_type_list(DataType p_data_type) const; + + Vector<String> _get_type_variation_list(const StringName &p_theme_type) const; Vector<String> _get_type_list() const; protected: @@ -197,11 +201,16 @@ public: void add_theme_item_type(DataType p_data_type, const StringName &p_theme_type); void get_theme_item_type_list(DataType p_data_type, List<StringName> *p_list) const; + void set_type_variation(const StringName &p_theme_type, const StringName &p_base_type); + bool is_type_variation(const StringName &p_theme_type, const StringName &p_base_type) const; + void clear_type_variation(const StringName &p_theme_type); + StringName get_type_variation_base(const StringName &p_theme_type) const; + void get_type_variation_list(const StringName &p_base_type, List<StringName> *p_list) const; + void get_type_list(List<StringName> *p_list) const; - static void get_type_dependencies(const StringName &p_theme_type, List<StringName> *p_list); + void get_type_dependencies(const StringName &p_base_type, const StringName &p_type_variant, List<StringName> *p_list); - void copy_default_theme(); - void copy_theme(const Ref<Theme> &p_other); + void merge_with(const Ref<Theme> &p_other); void clear(); Theme(); diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index 4f854ff229..918fc3fe9c 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -41,6 +41,8 @@ /////////////////////////////// TileSet ////////////////////////////////////// +const int TileSet::INVALID_SOURCE = -1; + const char *TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[] = { "right_side", "right_corner", @@ -60,11 +62,6 @@ const char *TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[] = { "top_right_corner" }; -// --- Plugins --- -Vector<TileSetPlugin *> TileSet::get_tile_set_atlas_plugins() const { - return tile_set_plugins_vector; -} - // -- Shape and layout -- void TileSet::set_tile_shape(TileSet::TileShape p_shape) { tile_shape = p_shape; @@ -127,8 +124,8 @@ void TileSet::_compute_next_source_id() { // Sources management int TileSet::add_source(Ref<TileSetSource> p_tile_set_source, int p_atlas_source_id_override) { - ERR_FAIL_COND_V(!p_tile_set_source.is_valid(), -1); - ERR_FAIL_COND_V_MSG(p_atlas_source_id_override >= 0 && (sources.has(p_atlas_source_id_override)), -1, vformat("Cannot create TileSet atlas source. Another atlas source exists with id %d.", p_atlas_source_id_override)); + ERR_FAIL_COND_V(!p_tile_set_source.is_valid(), TileSet::INVALID_SOURCE); + ERR_FAIL_COND_V_MSG(p_atlas_source_id_override >= 0 && (sources.has(p_atlas_source_id_override)), TileSet::INVALID_SOURCE, vformat("Cannot create TileSet atlas source. Another atlas source exists with id %d.", p_atlas_source_id_override)); int new_source_id = p_atlas_source_id_override >= 0 ? p_atlas_source_id_override : next_source_id; sources[new_source_id] = p_tile_set_source; @@ -191,7 +188,7 @@ int TileSet::get_source_count() const { } int TileSet::get_source_id(int p_index) const { - ERR_FAIL_INDEX_V(p_index, source_ids.size(), -1); + ERR_FAIL_INDEX_V(p_index, source_ids.size(), TileSet::INVALID_SOURCE); return source_ids[p_index]; } @@ -203,40 +200,51 @@ void TileSet::set_uv_clipping(bool p_uv_clipping) { uv_clipping = p_uv_clipping; emit_changed(); } + bool TileSet::is_uv_clipping() const { return uv_clipping; }; -void TileSet::set_y_sorting(bool p_y_sort) { - if (y_sorting == p_y_sort) { - return; - } - y_sorting = p_y_sort; - emit_changed(); -} -bool TileSet::is_y_sorting() const { - return y_sorting; +int TileSet::get_occlusion_layers_count() const { + return occlusion_layers.size(); }; -void TileSet::set_occlusion_layers_count(int p_occlusion_layers_count) { - ERR_FAIL_COND(p_occlusion_layers_count < 0); - if (occlusion_layers.size() == p_occlusion_layers_count) { - return; +void TileSet::add_occlusion_layer(int p_index) { + if (p_index < 0) { + p_index = occlusion_layers.size(); } + ERR_FAIL_INDEX(p_index, occlusion_layers.size() + 1); + occlusion_layers.insert(p_index, OcclusionLayer()); - occlusion_layers.resize(p_occlusion_layers_count); - - for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { - E_source->get()->notify_tile_data_properties_should_change(); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->add_occlusion_layer(p_index); } notify_property_list_changed(); emit_changed(); } -int TileSet::get_occlusion_layers_count() const { - return occlusion_layers.size(); -}; +void TileSet::move_occlusion_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, occlusion_layers.size()); + ERR_FAIL_INDEX(p_to_pos, occlusion_layers.size() + 1); + occlusion_layers.insert(p_to_pos, occlusion_layers[p_from_index]); + occlusion_layers.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->move_occlusion_layer(p_from_index, p_to_pos); + } + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::remove_occlusion_layer(int p_index) { + ERR_FAIL_INDEX(p_index, occlusion_layers.size()); + occlusion_layers.remove(p_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->remove_occlusion_layer(p_index); + } + notify_property_list_changed(); + emit_changed(); +} void TileSet::set_occlusion_layer_light_mask(int p_layer_index, int p_light_mask) { ERR_FAIL_INDEX(p_layer_index, occlusion_layers.size()); @@ -249,7 +257,7 @@ int TileSet::get_occlusion_layer_light_mask(int p_layer_index) const { return occlusion_layers[p_layer_index].light_mask; } -void TileSet::set_occlusion_layer_sdf_collision(int p_layer_index, int p_sdf_collision) { +void TileSet::set_occlusion_layer_sdf_collision(int p_layer_index, bool p_sdf_collision) { ERR_FAIL_INDEX(p_layer_index, occlusion_layers.size()); occlusion_layers.write[p_layer_index].sdf_collision = p_sdf_collision; emit_changed(); @@ -260,25 +268,45 @@ bool TileSet::get_occlusion_layer_sdf_collision(int p_layer_index) const { return occlusion_layers[p_layer_index].sdf_collision; } -// Physics -void TileSet::set_physics_layers_count(int p_physics_layers_count) { - ERR_FAIL_COND(p_physics_layers_count < 0); - if (physics_layers.size() == p_physics_layers_count) { - return; - } +int TileSet::get_physics_layers_count() const { + return physics_layers.size(); +} - physics_layers.resize(p_physics_layers_count); +void TileSet::add_physics_layer(int p_index) { + if (p_index < 0) { + p_index = physics_layers.size(); + } + ERR_FAIL_INDEX(p_index, physics_layers.size() + 1); + physics_layers.insert(p_index, PhysicsLayer()); - for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { - E_source->get()->notify_tile_data_properties_should_change(); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->add_physics_layer(p_index); } notify_property_list_changed(); emit_changed(); } -int TileSet::get_physics_layers_count() const { - return physics_layers.size(); +void TileSet::move_physics_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, physics_layers.size()); + ERR_FAIL_INDEX(p_to_pos, physics_layers.size() + 1); + physics_layers.insert(p_to_pos, physics_layers[p_from_index]); + physics_layers.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->move_physics_layer(p_from_index, p_to_pos); + } + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::remove_physics_layer(int p_index) { + ERR_FAIL_INDEX(p_index, physics_layers.size()); + physics_layers.remove(p_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->remove_physics_layer(p_index); + } + notify_property_list_changed(); + emit_changed(); } void TileSet::set_physics_layer_collision_layer(int p_layer_index, uint32_t p_layer) { @@ -314,17 +342,45 @@ Ref<PhysicsMaterial> TileSet::get_physics_layer_physics_material(int p_layer_ind } // Terrains -void TileSet::set_terrain_sets_count(int p_terrains_sets_count) { - ERR_FAIL_COND(p_terrains_sets_count < 0); +int TileSet::get_terrain_sets_count() const { + return terrain_sets.size(); +} + +void TileSet::add_terrain_set(int p_index) { + if (p_index < 0) { + p_index = terrain_sets.size(); + } + ERR_FAIL_INDEX(p_index, terrain_sets.size() + 1); + terrain_sets.insert(p_index, TerrainSet()); - terrain_sets.resize(p_terrains_sets_count); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->add_terrain_set(p_index); + } notify_property_list_changed(); emit_changed(); } -int TileSet::get_terrain_sets_count() const { - return terrain_sets.size(); +void TileSet::move_terrain_set(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, terrain_sets.size()); + ERR_FAIL_INDEX(p_to_pos, terrain_sets.size() + 1); + terrain_sets.insert(p_to_pos, terrain_sets[p_from_index]); + terrain_sets.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->move_terrain_set(p_from_index, p_to_pos); + } + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::remove_terrain_set(int p_index) { + ERR_FAIL_INDEX(p_index, terrain_sets.size()); + terrain_sets.remove(p_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->remove_terrain_set(p_index); + } + notify_property_list_changed(); + emit_changed(); } void TileSet::set_terrain_set_mode(int p_terrain_set, TerrainMode p_terrain_mode) { @@ -343,36 +399,61 @@ TileSet::TerrainMode TileSet::get_terrain_set_mode(int p_terrain_set) const { return terrain_sets[p_terrain_set].mode; } -void TileSet::set_terrains_count(int p_terrain_set, int p_terrains_layers_count) { +int TileSet::get_terrains_count(int p_terrain_set) const { + ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), -1); + return terrain_sets[p_terrain_set].terrains.size(); +} + +void TileSet::add_terrain(int p_terrain_set, int p_index) { ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size()); - ERR_FAIL_COND(p_terrains_layers_count < 0); - if (terrain_sets[p_terrain_set].terrains.size() == p_terrains_layers_count) { - return; + Vector<Terrain> &terrains = terrain_sets.write[p_terrain_set].terrains; + if (p_index < 0) { + p_index = terrains.size(); } - - int old_size = terrain_sets[p_terrain_set].terrains.size(); - terrain_sets.write[p_terrain_set].terrains.resize(p_terrains_layers_count); + ERR_FAIL_INDEX(p_index, terrains.size() + 1); + terrains.insert(p_index, Terrain()); // Default name and color - for (int i = old_size; i < terrain_sets.write[p_terrain_set].terrains.size(); i++) { - float hue_rotate = (i * 2 % 16) / 16.0; - Color c; - c.set_hsv(Math::fmod(float(hue_rotate), float(1.0)), 0.5, 0.5); - terrain_sets.write[p_terrain_set].terrains.write[i].color = c; - terrain_sets.write[p_terrain_set].terrains.write[i].name = String(vformat("Terrain %d", i)); - } + float hue_rotate = (terrains.size() % 16) / 16.0; + Color c; + c.set_hsv(Math::fmod(float(hue_rotate), float(1.0)), 0.5, 0.5); + terrains.write[p_index].color = c; + terrains.write[p_index].name = String(vformat("Terrain %d", p_index)); - for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { - E_source->get()->notify_tile_data_properties_should_change(); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->add_terrain(p_terrain_set, p_index); } notify_property_list_changed(); emit_changed(); } -int TileSet::get_terrains_count(int p_terrain_set) const { - ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), -1); - return terrain_sets[p_terrain_set].terrains.size(); +void TileSet::move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size()); + Vector<Terrain> &terrains = terrain_sets.write[p_terrain_set].terrains; + + ERR_FAIL_INDEX(p_from_index, terrains.size()); + ERR_FAIL_INDEX(p_to_pos, terrains.size() + 1); + terrains.insert(p_to_pos, terrains[p_from_index]); + terrains.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->move_terrain(p_terrain_set, p_from_index, p_to_pos); + } + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::remove_terrain(int p_terrain_set, int p_index) { + ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size()); + Vector<Terrain> &terrains = terrain_sets.write[p_terrain_set].terrains; + + ERR_FAIL_INDEX(p_index, terrains.size()); + terrains.remove(p_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->remove_terrain(p_terrain_set, p_index); + } + notify_property_list_changed(); + emit_changed(); } void TileSet::set_terrain_name(int p_terrain_set, int p_terrain_index, String p_name) { @@ -498,24 +579,45 @@ bool TileSet::is_valid_peering_bit_terrain(int p_terrain_set, TileSet::CellNeigh } // Navigation -void TileSet::set_navigation_layers_count(int p_navigation_layers_count) { - ERR_FAIL_COND(p_navigation_layers_count < 0); - if (navigation_layers.size() == p_navigation_layers_count) { - return; - } +int TileSet::get_navigation_layers_count() const { + return navigation_layers.size(); +} - navigation_layers.resize(p_navigation_layers_count); +void TileSet::add_navigation_layer(int p_index) { + if (p_index < 0) { + p_index = navigation_layers.size(); + } + ERR_FAIL_INDEX(p_index, navigation_layers.size() + 1); + navigation_layers.insert(p_index, NavigationLayer()); - for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { - E_source->get()->notify_tile_data_properties_should_change(); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->add_navigation_layer(p_index); } notify_property_list_changed(); emit_changed(); } -int TileSet::get_navigation_layers_count() const { - return navigation_layers.size(); +void TileSet::move_navigation_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, navigation_layers.size()); + ERR_FAIL_INDEX(p_to_pos, navigation_layers.size() + 1); + navigation_layers.insert(p_to_pos, navigation_layers[p_from_index]); + navigation_layers.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->move_navigation_layer(p_from_index, p_to_pos); + } + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::remove_navigation_layer(int p_index) { + ERR_FAIL_INDEX(p_index, navigation_layers.size()); + navigation_layers.remove(p_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->remove_navigation_layer(p_index); + } + notify_property_list_changed(); + emit_changed(); } void TileSet::set_navigation_layer_layers(int p_layer_index, uint32_t p_layers) { @@ -530,30 +632,52 @@ uint32_t TileSet::get_navigation_layer_layers(int p_layer_index) const { } // Custom data. -void TileSet::set_custom_data_layers_count(int p_custom_data_layers_count) { - ERR_FAIL_COND(p_custom_data_layers_count < 0); - if (custom_data_layers.size() == p_custom_data_layers_count) { - return; - } - - custom_data_layers.resize(p_custom_data_layers_count); +int TileSet::get_custom_data_layers_count() const { + return custom_data_layers.size(); +} - for (Map<String, int>::Element *E = custom_data_layers_by_name.front(); E; E = E->next()) { - if (E->get() >= custom_data_layers.size()) { - custom_data_layers_by_name.erase(E); - } +void TileSet::add_custom_data_layer(int p_index) { + if (p_index < 0) { + p_index = custom_data_layers.size(); } + ERR_FAIL_INDEX(p_index, custom_data_layers.size() + 1); + custom_data_layers.insert(p_index, CustomDataLayer()); - for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { - E_source->get()->notify_tile_data_properties_should_change(); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->add_custom_data_layer(p_index); } notify_property_list_changed(); emit_changed(); } -int TileSet::get_custom_data_layers_count() const { - return custom_data_layers.size(); +void TileSet::move_custom_data_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, custom_data_layers.size()); + ERR_FAIL_INDEX(p_to_pos, custom_data_layers.size() + 1); + custom_data_layers.insert(p_to_pos, custom_data_layers[p_from_index]); + custom_data_layers.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->move_custom_data_layer(p_from_index, p_to_pos); + } + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::remove_custom_data_layer(int p_index) { + ERR_FAIL_INDEX(p_index, custom_data_layers.size()); + custom_data_layers.remove(p_index); + for (KeyValue<String, int> E : custom_data_layers_by_name) { + if (E.value == p_index) { + custom_data_layers_by_name.erase(E.key); + break; + } + } + + for (KeyValue<int, Ref<TileSetSource>> source : sources) { + source.value->remove_custom_data_layer(p_index); + } + notify_property_list_changed(); + emit_changed(); } int TileSet::get_custom_data_layer_by_name(String p_value) const { @@ -607,13 +731,261 @@ Variant::Type TileSet::get_custom_data_type(int p_layer_id) const { return custom_data_layers[p_layer_id].type; } +void TileSet::set_source_level_tile_proxy(int p_source_from, int p_source_to) { + ERR_FAIL_COND(p_source_from == TileSet::INVALID_SOURCE || p_source_to == TileSet::INVALID_SOURCE); + + source_level_proxies[p_source_from] = p_source_to; + + emit_changed(); +} + +int TileSet::get_source_level_tile_proxy(int p_source_from) { + ERR_FAIL_COND_V(!source_level_proxies.has(p_source_from), TileSet::INVALID_SOURCE); + + return source_level_proxies[p_source_from]; +} + +bool TileSet::has_source_level_tile_proxy(int p_source_from) { + return source_level_proxies.has(p_source_from); +} + +void TileSet::remove_source_level_tile_proxy(int p_source_from) { + ERR_FAIL_COND(!source_level_proxies.has(p_source_from)); + + source_level_proxies.erase(p_source_from); + + emit_changed(); +} + +void TileSet::set_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_source_to, Vector2i p_coords_to) { + ERR_FAIL_COND(p_source_from == TileSet::INVALID_SOURCE || p_source_to == TileSet::INVALID_SOURCE); + ERR_FAIL_COND(p_coords_from == TileSetSource::INVALID_ATLAS_COORDS || p_coords_to == TileSetSource::INVALID_ATLAS_COORDS); + + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + + Array to; + to.push_back(p_source_to); + to.push_back(p_coords_to); + + coords_level_proxies[from] = to; + + emit_changed(); +} + +Array TileSet::get_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from) { + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + + ERR_FAIL_COND_V(!coords_level_proxies.has(from), Array()); + + return coords_level_proxies[from]; +} + +bool TileSet::has_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from) { + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + + return coords_level_proxies.has(from); +} + +void TileSet::remove_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from) { + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + + ERR_FAIL_COND(!coords_level_proxies.has(from)); + + coords_level_proxies.erase(from); + + emit_changed(); +} + +void TileSet::set_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from, int p_source_to, Vector2i p_coords_to, int p_alternative_to) { + ERR_FAIL_COND(p_source_from == TileSet::INVALID_SOURCE || p_source_to == TileSet::INVALID_SOURCE); + ERR_FAIL_COND(p_coords_from == TileSetSource::INVALID_ATLAS_COORDS || p_coords_to == TileSetSource::INVALID_ATLAS_COORDS); + + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + from.push_back(p_alternative_from); + + Array to; + to.push_back(p_source_to); + to.push_back(p_coords_to); + to.push_back(p_alternative_to); + + alternative_level_proxies[from] = to; + + emit_changed(); +} + +Array TileSet::get_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from) { + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + from.push_back(p_alternative_from); + + ERR_FAIL_COND_V(!alternative_level_proxies.has(from), Array()); + + return alternative_level_proxies[from]; +} + +bool TileSet::has_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from) { + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + from.push_back(p_alternative_from); + + return alternative_level_proxies.has(from); +} + +void TileSet::remove_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from) { + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + from.push_back(p_alternative_from); + + ERR_FAIL_COND(!alternative_level_proxies.has(from)); + + alternative_level_proxies.erase(from); + + emit_changed(); +} + +Array TileSet::get_source_level_tile_proxies() const { + Array output; + for (Map<int, int>::Element *E = source_level_proxies.front(); E; E = E->next()) { + Array proxy; + proxy.push_back(E->key()); + proxy.push_back(E->get()); + output.push_back(proxy); + } + return output; +} + +Array TileSet::get_coords_level_tile_proxies() const { + Array output; + for (Map<Array, Array>::Element *E = coords_level_proxies.front(); E; E = E->next()) { + Array proxy; + proxy.append_array(E->key()); + proxy.append_array(E->get()); + output.push_back(proxy); + } + return output; +} + +Array TileSet::get_alternative_level_tile_proxies() const { + Array output; + for (Map<Array, Array>::Element *E = alternative_level_proxies.front(); E; E = E->next()) { + Array proxy; + proxy.append_array(E->key()); + proxy.append_array(E->get()); + output.push_back(proxy); + } + return output; +} + +Array TileSet::map_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from) const { + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + from.push_back(p_alternative_from); + + // Check if the tile is valid, and if so, don't map the tile and return the input. + if (has_source(p_source_from)) { + Ref<TileSetSource> source = get_source(p_source_from); + if (source->has_tile(p_coords_from) && source->has_alternative_tile(p_coords_from, p_alternative_from)) { + return from; + } + } + + // Source, coords and alternative match. + if (alternative_level_proxies.has(from)) { + return alternative_level_proxies[from].duplicate(); + } + + // Source and coords match. + from.pop_back(); + if (coords_level_proxies.has(from)) { + Array output = coords_level_proxies[from].duplicate(); + output.push_back(p_alternative_from); + return output; + } + + // Source matches. + if (source_level_proxies.has(p_source_from)) { + Array output; + output.push_back(source_level_proxies[p_source_from]); + output.push_back(p_coords_from); + output.push_back(p_alternative_from); + return output; + } + + Array output; + output.push_back(p_source_from); + output.push_back(p_coords_from); + output.push_back(p_alternative_from); + return output; +} + +void TileSet::cleanup_invalid_tile_proxies() { + // Source level. + Vector<int> source_to_remove; + for (Map<int, int>::Element *E = source_level_proxies.front(); E; E = E->next()) { + if (has_source(E->key())) { + source_to_remove.append(E->key()); + } + } + for (int i = 0; i < source_to_remove.size(); i++) { + remove_source_level_tile_proxy(source_to_remove[i]); + } + + // Coords level. + Vector<Array> coords_to_remove; + for (Map<Array, Array>::Element *E = coords_level_proxies.front(); E; E = E->next()) { + Array a = E->key(); + if (has_source(a[0]) && get_source(a[0])->has_tile(a[1])) { + coords_to_remove.append(a); + } + } + for (int i = 0; i < coords_to_remove.size(); i++) { + Array a = coords_to_remove[i]; + remove_coords_level_tile_proxy(a[0], a[1]); + } + + // Alternative level. + Vector<Array> alternative_to_remove; + for (Map<Array, Array>::Element *E = alternative_level_proxies.front(); E; E = E->next()) { + Array a = E->key(); + if (has_source(a[0]) && get_source(a[0])->has_tile(a[1]) && get_source(a[0])->has_alternative_tile(a[1], a[2])) { + alternative_to_remove.append(a); + } + } + for (int i = 0; i < alternative_to_remove.size(); i++) { + Array a = alternative_to_remove[i]; + remove_alternative_level_tile_proxy(a[0], a[1], a[2]); + } +} + +void TileSet::clear_tile_proxies() { + source_level_proxies.clear(); + coords_level_proxies.clear(); + alternative_level_proxies.clear(); + + emit_changed(); +} + Vector<Vector2> TileSet::get_tile_shape_polygon() { Vector<Vector2> points; if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { - points.append(Vector2(0.0, 0.0)); - points.append(Vector2(1.0, 0.0)); - points.append(Vector2(1.0, 1.0)); - points.append(Vector2(0.0, 1.0)); + points.append(Vector2(-0.5, -0.5)); + points.append(Vector2(0.5, -0.5)); + points.append(Vector2(0.5, 0.5)); + points.append(Vector2(-0.5, 0.5)); } else { float overlap = 0.0; switch (tile_shape) { @@ -630,31 +1002,24 @@ Vector<Vector2> TileSet::get_tile_shape_polygon() { break; } - points.append(Vector2(0.5, 0.0)); - points.append(Vector2(0.0, overlap)); - points.append(Vector2(0.0, 1.0 - overlap)); - points.append(Vector2(0.5, 1.0)); - points.append(Vector2(1.0, 1.0 - overlap)); - points.append(Vector2(1.0, overlap)); - points.append(Vector2(0.5, 0.0)); + points.append(Vector2(0.0, -0.5)); + points.append(Vector2(-0.5, overlap - 0.5)); + points.append(Vector2(-0.5, 0.5 - overlap)); + points.append(Vector2(0.0, 0.5)); + points.append(Vector2(0.5, 0.5 - overlap)); + points.append(Vector2(0.5, overlap - 0.5)); if (get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL) { for (int i = 0; i < points.size(); i++) { points.write[i] = Vector2(points[i].y, points[i].x); } } } - for (int i = 0; i < points.size(); i++) { - points.write[i] = points[i] * tile_size - tile_size / 2; - } return points; } -void TileSet::draw_tile_shape(CanvasItem *p_canvas_item, Rect2 p_region, Color p_color, bool p_filled, Ref<Texture2D> p_texture) { +void TileSet::draw_tile_shape(CanvasItem *p_canvas_item, Transform2D p_transform, Color p_color, bool p_filled, Ref<Texture2D> p_texture) { if (tile_meshes_dirty) { Vector<Vector2> uvs = get_tile_shape_polygon(); - for (int i = 0; i < uvs.size(); i++) { - uvs.write[i] = (uvs[i] + tile_size / 2) / tile_size; - } Vector<Color> colors; colors.resize(uvs.size()); @@ -684,13 +1049,10 @@ void TileSet::draw_tile_shape(CanvasItem *p_canvas_item, Rect2 p_region, Color p tile_meshes_dirty = false; } - Transform2D xform; - xform.scale(p_region.size); - xform.set_origin(p_region.get_position()); if (p_filled) { - p_canvas_item->draw_mesh(tile_filled_mesh, p_texture, xform, p_color); + p_canvas_item->draw_mesh(tile_filled_mesh, p_texture, p_transform, p_color); } else { - p_canvas_item->draw_mesh(tile_lines_mesh, Ref<Texture2D>(), xform, p_color); + p_canvas_item->draw_mesh(tile_lines_mesh, Ref<Texture2D>(), p_transform, p_color); } } @@ -875,7 +1237,11 @@ Vector<Vector<Ref<Texture2D>>> TileSet::generate_terrains_icons(Size2i p_size) { if (is_valid_peering_bit_terrain(terrain_set, cell_neighbor)) { int terrain = tile_data->get_peering_bit_terrain(cell_neighbor); if (terrain >= 0) { - bit_counts[terrain] += 1; + if (terrain >= (int)bit_counts.size()) { + WARN_PRINT(vformat("Invalid peering bit terrain: %d", terrain)); + } else { + bit_counts[terrain] += 1; + } } } } @@ -1539,7 +1905,7 @@ const Vector2i TileSetSource::INVALID_ATLAS_COORDS = Vector2i(-1, -1); const int TileSetSource::INVALID_TILE_ALTERNATIVE = -1; #ifndef DISABLE_DEPRECATED -void TileSet::compatibility_conversion() { +void TileSet::_compatibility_conversion() { for (Map<int, CompatibilityTileData *>::Element *E = compatibility_data.front(); E; E = E->next()) { CompatibilityTileData *ctd = E->value(); @@ -1551,13 +1917,93 @@ void TileSet::compatibility_conversion() { // Handle each tile as a new source. Not optimal but at least it should stay compatible. switch (ctd->tile_mode) { - case 0: // SINGLE_TILE - // TODO - break; - case 1: // AUTO_TILE - // TODO - break; - case 2: // ATLAS_TILE + case COMPATIBILITY_TILE_MODE_SINGLE_TILE: { + atlas_source->set_margins(ctd->region.get_position()); + atlas_source->set_texture_region_size(ctd->region.get_size()); + + Vector2i coords; + for (int flags = 0; flags < 8; flags++) { + bool flip_h = flags & 1; + bool flip_v = flags & 2; + bool transpose = flags & 4; + + int alternative_tile = 0; + if (!atlas_source->has_tile(coords)) { + atlas_source->create_tile(coords); + } else { + alternative_tile = atlas_source->create_alternative_tile(coords); + } + + // Add to the mapping. + Array key_array; + key_array.push_back(flip_h); + key_array.push_back(flip_v); + key_array.push_back(transpose); + + Array value_array; + value_array.push_back(source_id); + value_array.push_back(coords); + 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()][key_array] = value_array; + compatibility_tilemap_mapping_tile_modes[E->key()] = COMPATIBILITY_TILE_MODE_SINGLE_TILE; + + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(coords, alternative_tile)); + + tile_data->set_flip_h(flip_h); + tile_data->set_flip_v(flip_v); + tile_data->set_transpose(transpose); + tile_data->set_material(ctd->material); + tile_data->set_modulate(ctd->modulate); + tile_data->set_z_index(ctd->z_index); + + if (ctd->occluder.is_valid()) { + if (get_occlusion_layers_count() < 1) { + add_occlusion_layer(); + } + tile_data->set_occluder(0, ctd->occluder); + } + if (ctd->navigation.is_valid()) { + if (get_navigation_layers_count() < 1) { + add_navigation_layer(); + } + tile_data->set_navigation_polygon(0, ctd->autotile_navpoly_map[coords]); + } + + tile_data->set_z_index(ctd->z_index); + + // Add the shapes. + if (ctd->shapes.size() > 0) { + if (get_physics_layers_count() < 1) { + add_physics_layer(); + } + } + for (int k = 0; k < ctd->shapes.size(); k++) { + CompatibilityShapeData csd = ctd->shapes[k]; + if (csd.autotile_coords == coords) { + Ref<ConvexPolygonShape2D> convex_shape = csd.shape; // Only ConvexPolygonShape2D are supported, which is the default type used by the 3.x editor + if (convex_shape.is_valid()) { + Vector<Vector2> polygon = convex_shape->get_points(); + for (int point_index = 0; point_index < polygon.size(); point_index++) { + polygon.write[point_index] = csd.transform.xform(polygon[point_index]); + } + tile_data->set_collision_polygons_count(0, tile_data->get_collision_polygons_count(0) + 1); + int index = tile_data->get_collision_polygons_count(0) - 1; + tile_data->set_collision_polygon_one_way(0, index, csd.one_way); + tile_data->set_collision_polygon_one_way_margin(0, index, csd.one_way_margin); + tile_data->set_collision_polygon_points(0, index, polygon); + } + } + } + } + } break; + case COMPATIBILITY_TILE_MODE_AUTO_TILE: { + // Not supported. It would need manual conversion. + } break; + case COMPATIBILITY_TILE_MODE_ATLAS_TILE: { atlas_source->set_margins(ctd->region.get_position()); atlas_source->set_separation(Vector2i(ctd->autotile_spacing, ctd->autotile_spacing)); atlas_source->set_texture_region_size(ctd->autotile_tile_size); @@ -1578,23 +2024,42 @@ void TileSet::compatibility_conversion() { } else { alternative_tile = atlas_source->create_alternative_tile(coords); } + + // Add to the mapping. + Array key_array; + key_array.push_back(coords); + key_array.push_back(flip_h); + key_array.push_back(flip_v); + key_array.push_back(transpose); + + Array value_array; + value_array.push_back(source_id); + value_array.push_back(coords); + 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()][key_array] = value_array; + compatibility_tilemap_mapping_tile_modes[E->key()] = COMPATIBILITY_TILE_MODE_ATLAS_TILE; + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(coords, alternative_tile)); tile_data->set_flip_h(flip_h); tile_data->set_flip_v(flip_v); tile_data->set_transpose(transpose); - tile_data->tile_set_material(ctd->material); + tile_data->set_material(ctd->material); tile_data->set_modulate(ctd->modulate); tile_data->set_z_index(ctd->z_index); if (ctd->autotile_occluder_map.has(coords)) { if (get_occlusion_layers_count() < 1) { - set_occlusion_layers_count(1); + add_occlusion_layer(); } tile_data->set_occluder(0, ctd->autotile_occluder_map[coords]); } if (ctd->autotile_navpoly_map.has(coords)) { if (get_navigation_layers_count() < 1) { - set_navigation_layers_count(1); + add_navigation_layer(); } tile_data->set_navigation_polygon(0, ctd->autotile_navpoly_map[coords]); } @@ -1608,7 +2073,7 @@ void TileSet::compatibility_conversion() { // Add the shapes. if (ctd->shapes.size() > 0) { if (get_physics_layers_count() < 1) { - set_physics_layers_count(1); + add_physics_layer(); } } for (int k = 0; k < ctd->shapes.size(); k++) { @@ -1641,7 +2106,7 @@ void TileSet::compatibility_conversion() { } } } - break; + } break; } // Offset all shapes @@ -1655,9 +2120,6 @@ void TileSet::compatibility_conversion() { convex->set_points(points); } } - - // Add the mapping to the map - compatibility_source_mapping.insert(E->key(), source_id); } // Reset compatibility data @@ -1666,14 +2128,50 @@ void TileSet::compatibility_conversion() { } compatibility_data = Map<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) { + Array cannot_convert_array; + cannot_convert_array.push_back(TileSet::INVALID_SOURCE); + cannot_convert_array.push_back(TileSetAtlasSource::INVALID_ATLAS_COORDS); + cannot_convert_array.push_back(TileSetAtlasSource::INVALID_TILE_ALTERNATIVE); + + if (!compatibility_tilemap_mapping.has(p_tile_id)) { + return cannot_convert_array; + } + + int tile_mode = compatibility_tilemap_mapping_tile_modes[p_tile_id]; + switch (tile_mode) { + case COMPATIBILITY_TILE_MODE_SINGLE_TILE: { + Array a; + a.push_back(p_flip_h); + a.push_back(p_flip_v); + a.push_back(p_transpose); + return compatibility_tilemap_mapping[p_tile_id][a]; + } + case COMPATIBILITY_TILE_MODE_AUTO_TILE: + return cannot_convert_array; + break; + case COMPATIBILITY_TILE_MODE_ATLAS_TILE: { + Array a; + a.push_back(p_coords); + a.push_back(p_flip_h); + a.push_back(p_flip_v); + a.push_back(p_transpose); + return compatibility_tilemap_mapping[p_tile_id][a]; + } + default: + return cannot_convert_array; + break; + } +}; + #endif // DISABLE_DEPRECATED bool TileSet::_set(const StringName &p_name, const Variant &p_value) { Vector<String> components = String(p_name).split("/", true, 2); #ifndef DISABLE_DEPRECATED - // TODO: THIS IS HOW WE CHECK IF WE HAVE A DEPRECATED RESOURCE - // This should be moved to a dedicated conversion system + // TODO: This should be moved to a dedicated conversion system (see #50691) if (components.size() >= 1 && components[0].is_valid_int()) { int id = components[0].to_int(); @@ -1809,29 +2307,23 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { /* // IGNORED FOR NOW, they seem duplicated data compared to the shapes array } else if (what == "shape") { - // TODO } else if (what == "shape_offset") { - // TODO } else if (what == "shape_transform") { - // TODO } else if (what == "shape_one_way") { - // TODO } else if (what == "shape_one_way_margin") { - // TODO } // IGNORED FOR NOW, maybe useless ? else if (what == "occluder_offset") { // Not } else if (what == "navigation_offset") { - // TODO } */ } else if (what == "z_index") { ctd->z_index = p_value; - // TODO: remove the conversion from here, it's not where it should be done - compatibility_conversion(); + // TODO: remove the conversion from here, it's not where it should be done (see #50691) + _compatibility_conversion(); } else { return false; } @@ -1845,15 +2337,15 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { ERR_FAIL_COND_V(index < 0, false); if (components[1] == "light_mask") { ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - if (index >= occlusion_layers.size()) { - set_occlusion_layers_count(index + 1); + while (index >= occlusion_layers.size()) { + add_occlusion_layer(); } set_occlusion_layer_light_mask(index, p_value); return true; } else if (components[1] == "sdf_collision") { ERR_FAIL_COND_V(p_value.get_type() != Variant::BOOL, false); - if (index >= occlusion_layers.size()) { - set_occlusion_layers_count(index + 1); + while (index >= occlusion_layers.size()) { + add_occlusion_layer(); } set_occlusion_layer_sdf_collision(index, p_value); return true; @@ -1864,23 +2356,22 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { ERR_FAIL_COND_V(index < 0, false); if (components[1] == "collision_layer") { ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - if (index >= physics_layers.size()) { - set_physics_layers_count(index + 1); + while (index >= physics_layers.size()) { + add_physics_layer(); } set_physics_layer_collision_layer(index, p_value); return true; } else if (components[1] == "collision_mask") { ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - if (index >= physics_layers.size()) { - set_physics_layers_count(index + 1); + while (index >= physics_layers.size()) { + add_physics_layer(); } set_physics_layer_collision_mask(index, p_value); return true; } else if (components[1] == "physics_material") { Ref<PhysicsMaterial> physics_material = p_value; - ERR_FAIL_COND_V(!physics_material.is_valid(), false); - if (index >= physics_layers.size()) { - set_physics_layers_count(index + 1); + while (index >= physics_layers.size()) { + add_physics_layer(); } set_physics_layer_physics_material(index, physics_material); return true; @@ -1891,37 +2382,30 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { ERR_FAIL_COND_V(terrain_set_index < 0, false); if (components[1] == "mode") { ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - if (terrain_set_index >= terrain_sets.size()) { - set_terrain_sets_count(terrain_set_index + 1); + while (terrain_set_index >= terrain_sets.size()) { + add_terrain_set(); } set_terrain_set_mode(terrain_set_index, TerrainMode(int(p_value))); - } else if (components[1] == "terrains_count") { - ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - if (terrain_set_index >= terrain_sets.size()) { - set_terrain_sets_count(terrain_set_index + 1); - } - set_terrains_count(terrain_set_index, p_value); - return true; } else if (components.size() >= 3 && components[1].begins_with("terrain_") && components[1].trim_prefix("terrain_").is_valid_int()) { int terrain_index = components[1].trim_prefix("terrain_").to_int(); ERR_FAIL_COND_V(terrain_index < 0, false); if (components[2] == "name") { ERR_FAIL_COND_V(p_value.get_type() != Variant::STRING, false); - if (terrain_set_index >= terrain_sets.size()) { - set_terrain_sets_count(terrain_set_index + 1); + while (terrain_set_index >= terrain_sets.size()) { + add_terrain_set(); } - if (terrain_index >= terrain_sets[terrain_set_index].terrains.size()) { - set_terrains_count(terrain_set_index, terrain_index + 1); + while (terrain_index >= terrain_sets[terrain_set_index].terrains.size()) { + add_terrain(terrain_set_index); } set_terrain_name(terrain_set_index, terrain_index, p_value); return true; } else if (components[2] == "color") { ERR_FAIL_COND_V(p_value.get_type() != Variant::COLOR, false); - if (terrain_set_index >= terrain_sets.size()) { - set_terrain_sets_count(terrain_set_index + 1); + while (terrain_set_index >= terrain_sets.size()) { + add_terrain_set(); } - if (terrain_index >= terrain_sets[terrain_set_index].terrains.size()) { - set_terrains_count(terrain_set_index, terrain_index + 1); + while (terrain_index >= terrain_sets[terrain_set_index].terrains.size()) { + add_terrain(terrain_set_index); } set_terrain_color(terrain_set_index, terrain_index, p_value); return true; @@ -1933,8 +2417,8 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { ERR_FAIL_COND_V(index < 0, false); if (components[1] == "layers") { ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - if (index >= navigation_layers.size()) { - set_navigation_layers_count(index + 1); + while (index >= navigation_layers.size()) { + add_navigation_layer(); } set_navigation_layer_layers(index, p_value); return true; @@ -1945,15 +2429,15 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { ERR_FAIL_COND_V(index < 0, false); if (components[1] == "name") { ERR_FAIL_COND_V(p_value.get_type() != Variant::STRING, false); - if (index >= custom_data_layers.size()) { - set_custom_data_layers_count(index + 1); + while (index >= custom_data_layers.size()) { + add_custom_data_layer(); } set_custom_data_name(index, p_value); return true; } else if (components[1] == "type") { ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - if (index >= custom_data_layers.size()) { - set_custom_data_layers_count(index + 1); + while (index >= custom_data_layers.size()) { + add_custom_data_layer(); } set_custom_data_type(index, Variant::Type(int(p_value))); return true; @@ -1966,6 +2450,31 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { add_source(p_value, source_id); } return true; + } else if (components.size() == 2 && components[0] == "tile_proxies") { + ERR_FAIL_COND_V(p_value.get_type() != Variant::ARRAY, false); + Array a = p_value; + ERR_FAIL_COND_V(a.size() % 2 != 0, false); + if (components[1] == "source_level") { + for (int i = 0; i < a.size(); i += 2) { + set_source_level_tile_proxy(a[i], a[i + 1]); + } + return true; + } else if (components[1] == "coords_level") { + for (int i = 0; i < a.size(); i += 2) { + Array key = a[i]; + Array value = a[i + 1]; + set_coords_level_tile_proxy(key[0], key[1], value[0], value[1]); + } + return true; + } else if (components[1] == "alternative_level") { + for (int i = 0; i < a.size(); i += 2) { + Array key = a[i]; + Array value = a[i + 1]; + set_alternative_level_tile_proxy(key[0], key[1], key[2], value[0], value[1], value[2]); + } + return true; + } + return false; } #ifndef DISABLE_DEPRECATED @@ -2016,9 +2525,6 @@ bool TileSet::_get(const StringName &p_name, Variant &r_ret) const { if (components[1] == "mode") { r_ret = get_terrain_set_mode(terrain_set_index); return true; - } else if (components[1] == "terrains_count") { - r_ret = get_terrains_count(terrain_set_index); - return true; } else if (components.size() >= 3 && components[1].begins_with("terrain_") && components[1].trim_prefix("terrain_").is_valid_int()) { int terrain_index = components[1].trim_prefix("terrain_").to_int(); if (terrain_index < 0 || terrain_index >= terrain_sets[terrain_set_index].terrains.size()) { @@ -2065,6 +2571,33 @@ bool TileSet::_get(const StringName &p_name, Variant &r_ret) const { } else { return false; } + } else if (components.size() == 2 && components[0] == "tile_proxies") { + if (components[1] == "source_level") { + Array a; + for (Map<int, int>::Element *E = source_level_proxies.front(); E; E = E->next()) { + a.push_back(E->key()); + a.push_back(E->get()); + } + r_ret = a; + return true; + } else if (components[1] == "coords_level") { + Array a; + for (Map<Array, Array>::Element *E = coords_level_proxies.front(); E; E = E->next()) { + a.push_back(E->key()); + a.push_back(E->get()); + } + r_ret = a; + return true; + } else if (components[1] == "alternative_level") { + Array a; + for (Map<Array, Array>::Element *E = alternative_level_proxies.front(); E; E = E->next()) { + a.push_back(E->key()); + a.push_back(E->get()); + } + r_ret = a; + return true; + } + return false; } return false; @@ -2109,7 +2642,7 @@ void TileSet::_get_property_list(List<PropertyInfo> *p_list) const { 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/terrains_count", terrain_set_index), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); + 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))); p_list->push_back(PropertyInfo(Variant::COLOR, vformat("terrain_set_%d/terrain_%d/color", terrain_set_index, terrain_index))); @@ -2138,18 +2671,25 @@ void TileSet::_get_property_list(List<PropertyInfo> *p_list) const { for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { p_list->push_back(PropertyInfo(Variant::INT, vformat("sources/%d", E_source->key()), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); } + + // Tile Proxies. + // Note: proxies need to be set after sources are set. + p_list->push_back(PropertyInfo(Variant::NIL, "Tile Proxies", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); + p_list->push_back(PropertyInfo(Variant::ARRAY, "tile_proxies/source_level", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::ARRAY, "tile_proxies/coords_level", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::ARRAY, "tile_proxies/alternative_level", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); } void TileSet::_bind_methods() { // Sources management. ClassDB::bind_method(D_METHOD("get_next_source_id"), &TileSet::get_next_source_id); - ClassDB::bind_method(D_METHOD("add_source", "atlas_source_id_override"), &TileSet::add_source, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("add_source", "source", "atlas_source_id_override"), &TileSet::add_source, DEFVAL(TileSet::INVALID_SOURCE)); ClassDB::bind_method(D_METHOD("remove_source", "source_id"), &TileSet::remove_source); - ClassDB::bind_method(D_METHOD("set_source_id", "source_id"), &TileSet::set_source_id); + ClassDB::bind_method(D_METHOD("set_source_id", "source_id", "new_source_id"), &TileSet::set_source_id); ClassDB::bind_method(D_METHOD("get_source_count"), &TileSet::get_source_count); ClassDB::bind_method(D_METHOD("get_source_id", "index"), &TileSet::get_source_id); - ClassDB::bind_method(D_METHOD("has_source", "index"), &TileSet::has_source); - ClassDB::bind_method(D_METHOD("get_source", "index"), &TileSet::get_source); + ClassDB::bind_method(D_METHOD("has_source", "source_id"), &TileSet::has_source); + ClassDB::bind_method(D_METHOD("get_source", "source_id"), &TileSet::get_source); // Shape and layout. ClassDB::bind_method(D_METHOD("set_tile_shape", "shape"), &TileSet::set_tile_shape); @@ -2169,19 +2709,21 @@ void TileSet::_bind_methods() { // Rendering. ClassDB::bind_method(D_METHOD("set_uv_clipping", "uv_clipping"), &TileSet::set_uv_clipping); ClassDB::bind_method(D_METHOD("is_uv_clipping"), &TileSet::is_uv_clipping); - ClassDB::bind_method(D_METHOD("set_y_sorting", "y_sorting"), &TileSet::set_y_sorting); - ClassDB::bind_method(D_METHOD("is_y_sorting"), &TileSet::is_y_sorting); - ClassDB::bind_method(D_METHOD("set_occlusion_layers_count", "occlusion_layers_count"), &TileSet::set_occlusion_layers_count); ClassDB::bind_method(D_METHOD("get_occlusion_layers_count"), &TileSet::get_occlusion_layers_count); + ClassDB::bind_method(D_METHOD("add_occlusion_layer", "to_position"), &TileSet::add_occlusion_layer, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("move_occlusion_layer", "layer_index", "to_position"), &TileSet::move_occlusion_layer); + ClassDB::bind_method(D_METHOD("remove_occlusion_layer", "layer_index"), &TileSet::remove_occlusion_layer); ClassDB::bind_method(D_METHOD("set_occlusion_layer_light_mask", "layer_index", "light_mask"), &TileSet::set_occlusion_layer_light_mask); - ClassDB::bind_method(D_METHOD("get_occlusion_layer_light_mask"), &TileSet::get_occlusion_layer_light_mask); + ClassDB::bind_method(D_METHOD("get_occlusion_layer_light_mask", "layer_index"), &TileSet::get_occlusion_layer_light_mask); ClassDB::bind_method(D_METHOD("set_occlusion_layer_sdf_collision", "layer_index", "sdf_collision"), &TileSet::set_occlusion_layer_sdf_collision); - ClassDB::bind_method(D_METHOD("get_occlusion_layer_sdf_collision"), &TileSet::get_occlusion_layer_sdf_collision); + ClassDB::bind_method(D_METHOD("get_occlusion_layer_sdf_collision", "layer_index"), &TileSet::get_occlusion_layer_sdf_collision); // Physics - ClassDB::bind_method(D_METHOD("set_physics_layers_count", "physics_layers_count"), &TileSet::set_physics_layers_count); ClassDB::bind_method(D_METHOD("get_physics_layers_count"), &TileSet::get_physics_layers_count); + ClassDB::bind_method(D_METHOD("add_physics_layer", "to_position"), &TileSet::add_physics_layer, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("move_physics_layer", "layer_index", "to_position"), &TileSet::move_physics_layer); + ClassDB::bind_method(D_METHOD("remove_physics_layer", "layer_index"), &TileSet::remove_physics_layer); ClassDB::bind_method(D_METHOD("set_physics_layer_collision_layer", "layer_index", "layer"), &TileSet::set_physics_layer_collision_layer); ClassDB::bind_method(D_METHOD("get_physics_layer_collision_layer", "layer_index"), &TileSet::get_physics_layer_collision_layer); ClassDB::bind_method(D_METHOD("set_physics_layer_collision_mask", "layer_index", "mask"), &TileSet::set_physics_layer_collision_mask); @@ -2190,44 +2732,72 @@ void TileSet::_bind_methods() { ClassDB::bind_method(D_METHOD("get_physics_layer_physics_material", "layer_index"), &TileSet::get_physics_layer_physics_material); // Terrains - ClassDB::bind_method(D_METHOD("set_terrain_sets_count", "terrain_sets_count"), &TileSet::set_terrain_sets_count); ClassDB::bind_method(D_METHOD("get_terrain_sets_count"), &TileSet::get_terrain_sets_count); + ClassDB::bind_method(D_METHOD("add_terrain_set", "to_position"), &TileSet::add_terrain_set, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("move_terrain_set", "terrain_set", "to_position"), &TileSet::move_terrain_set); + ClassDB::bind_method(D_METHOD("remove_terrain_set", "terrain_set"), &TileSet::remove_terrain_set); ClassDB::bind_method(D_METHOD("set_terrain_set_mode", "terrain_set", "mode"), &TileSet::set_terrain_set_mode); ClassDB::bind_method(D_METHOD("get_terrain_set_mode", "terrain_set"), &TileSet::get_terrain_set_mode); - ClassDB::bind_method(D_METHOD("set_terrains_count", "terrain_set", "terrains_count"), &TileSet::set_terrains_count); ClassDB::bind_method(D_METHOD("get_terrains_count", "terrain_set"), &TileSet::get_terrains_count); + ClassDB::bind_method(D_METHOD("add_terrain", "terrain_set", "to_position"), &TileSet::add_terrain, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("move_terrain", "terrain_set", "terrain_index", "to_position"), &TileSet::move_terrain); + ClassDB::bind_method(D_METHOD("remove_terrain", "terrain_set", "terrain_index"), &TileSet::remove_terrain); ClassDB::bind_method(D_METHOD("set_terrain_name", "terrain_set", "terrain_index", "name"), &TileSet::set_terrain_name); ClassDB::bind_method(D_METHOD("get_terrain_name", "terrain_set", "terrain_index"), &TileSet::get_terrain_name); ClassDB::bind_method(D_METHOD("set_terrain_color", "terrain_set", "terrain_index", "color"), &TileSet::set_terrain_color); ClassDB::bind_method(D_METHOD("get_terrain_color", "terrain_set", "terrain_index"), &TileSet::get_terrain_color); // Navigation - ClassDB::bind_method(D_METHOD("set_navigation_layers_count", "navigation_layers_count"), &TileSet::set_navigation_layers_count); ClassDB::bind_method(D_METHOD("get_navigation_layers_count"), &TileSet::get_navigation_layers_count); + ClassDB::bind_method(D_METHOD("add_navigation_layer", "to_position"), &TileSet::add_navigation_layer, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("move_navigation_layer", "layer_index", "to_position"), &TileSet::move_navigation_layer); + ClassDB::bind_method(D_METHOD("remove_navigation_layer", "layer_index"), &TileSet::remove_navigation_layer); ClassDB::bind_method(D_METHOD("set_navigation_layer_layers", "layer_index", "layers"), &TileSet::set_navigation_layer_layers); ClassDB::bind_method(D_METHOD("get_navigation_layer_layers", "layer_index"), &TileSet::get_navigation_layer_layers); // Custom data - ClassDB::bind_method(D_METHOD("set_custom_data_layers_count", "custom_data_layers_count"), &TileSet::set_custom_data_layers_count); ClassDB::bind_method(D_METHOD("get_custom_data_layers_count"), &TileSet::get_custom_data_layers_count); + ClassDB::bind_method(D_METHOD("add_custom_data_layer", "to_position"), &TileSet::add_custom_data_layer, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("move_custom_data_layer", "layer_index", "to_position"), &TileSet::move_custom_data_layer); + ClassDB::bind_method(D_METHOD("remove_custom_data_layer", "layer_index"), &TileSet::remove_custom_data_layer); + + // Tile proxies + ClassDB::bind_method(D_METHOD("set_source_level_tile_proxy", "source_from", "source_to"), &TileSet::set_source_level_tile_proxy); + ClassDB::bind_method(D_METHOD("get_source_level_tile_proxy", "source_from"), &TileSet::get_source_level_tile_proxy); + ClassDB::bind_method(D_METHOD("has_source_level_tile_proxy", "source_from"), &TileSet::has_source_level_tile_proxy); + ClassDB::bind_method(D_METHOD("remove_source_level_tile_proxy", "source_from"), &TileSet::remove_source_level_tile_proxy); + + ClassDB::bind_method(D_METHOD("set_coords_level_tile_proxy", "p_source_from", "coords_from", "source_to", "coords_to"), &TileSet::set_coords_level_tile_proxy); + ClassDB::bind_method(D_METHOD("get_coords_level_tile_proxy", "source_from", "coords_from"), &TileSet::get_coords_level_tile_proxy); + ClassDB::bind_method(D_METHOD("has_coords_level_tile_proxy", "source_from", "coords_from"), &TileSet::has_coords_level_tile_proxy); + ClassDB::bind_method(D_METHOD("remove_coords_level_tile_proxy", "source_from", "coords_from"), &TileSet::remove_coords_level_tile_proxy); + + ClassDB::bind_method(D_METHOD("set_alternative_level_tile_proxy", "source_from", "coords_from", "alternative_from", "source_to", "coords_to", "alternative_to"), &TileSet::set_alternative_level_tile_proxy); + ClassDB::bind_method(D_METHOD("get_alternative_level_tile_proxy", "source_from", "coords_from", "alternative_from"), &TileSet::get_alternative_level_tile_proxy); + ClassDB::bind_method(D_METHOD("has_alternative_level_tile_proxy", "source_from", "coords_from", "alternative_from"), &TileSet::has_alternative_level_tile_proxy); + ClassDB::bind_method(D_METHOD("remove_alternative_level_tile_proxy", "source_from", "coords_from", "alternative_from"), &TileSet::remove_alternative_level_tile_proxy); + + ClassDB::bind_method(D_METHOD("map_tile_proxy", "source_from", "coords_from", "alternative_from"), &TileSet::map_tile_proxy); + + ClassDB::bind_method(D_METHOD("cleanup_invalid_tile_proxies"), &TileSet::cleanup_invalid_tile_proxies); + ClassDB::bind_method(D_METHOD("clear_tile_proxies"), &TileSet::clear_tile_proxies); ADD_GROUP("Rendering", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uv_clipping"), "set_uv_clipping", "is_uv_clipping"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "y_sorting"), "set_y_sorting", "is_y_sorting"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "occlusion_layers_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_occlusion_layers_count", "get_occlusion_layers_count"); + ADD_ARRAY("occlusion_layers", "occlusion_layer_"); ADD_GROUP("Physics", ""); - ADD_PROPERTY(PropertyInfo(Variant::INT, "physics_layers_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_physics_layers_count", "get_physics_layers_count"); + ADD_ARRAY("physics_layers", "physics_layer_"); ADD_GROUP("Terrains", ""); - ADD_PROPERTY(PropertyInfo(Variant::INT, "terrains_sets_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_terrain_sets_count", "get_terrain_sets_count"); + ADD_ARRAY("terrain_sets", "terrain_set_"); ADD_GROUP("Navigation", ""); - ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_navigation_layers_count", "get_navigation_layers_count"); + ADD_ARRAY("navigation_layers", "navigation_layer_"); ADD_GROUP("Custom data", ""); - ADD_PROPERTY(PropertyInfo(Variant::INT, "custom_data_layers_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_custom_data_layers_count", "get_custom_data_layers_count"); + ADD_ARRAY("custom_data_layers", "custom_data_layer_"); // -- Enum binding -- BIND_ENUM_CONSTANT(TILE_SHAPE_SQUARE); @@ -2245,22 +2815,22 @@ void TileSet::_bind_methods() { BIND_ENUM_CONSTANT(TILE_OFFSET_AXIS_HORIZONTAL); BIND_ENUM_CONSTANT(TILE_OFFSET_AXIS_VERTICAL); - BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_RIGHT_SIDE); - BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_RIGHT_CORNER); - BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE); - BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER); - BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE); - BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_BOTTOM_CORNER); - BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE); - BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER); - BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_LEFT_SIDE); - BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_LEFT_CORNER); - BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER); - BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_TOP_SIDE); - BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_TOP_CORNER); - BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); - BIND_ENUM_CONSTANT(TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_RIGHT_SIDE); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_RIGHT_CORNER); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_BOTTOM_SIDE); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_BOTTOM_CORNER); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_BOTTOM_LEFT_SIDE); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_BOTTOM_LEFT_CORNER); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_LEFT_SIDE); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_LEFT_CORNER); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_TOP_LEFT_SIDE); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_TOP_LEFT_CORNER); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_TOP_SIDE); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_TOP_CORNER); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_TOP_RIGHT_SIDE); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_TOP_RIGHT_CORNER); BIND_ENUM_CONSTANT(TERRAIN_MODE_MATCH_CORNERS_AND_SIDES); BIND_ENUM_CONSTANT(TERRAIN_MODE_MATCH_CORNERS); @@ -2271,12 +2841,6 @@ TileSet::TileSet() { // Instantiate the tile meshes. tile_lines_mesh.instantiate(); tile_filled_mesh.instantiate(); - - // Instanciate and list all plugins. - tile_set_plugins_vector.append(memnew(TileSetPluginAtlasRendering)); - tile_set_plugins_vector.append(memnew(TileSetPluginAtlasPhysics)); - tile_set_plugins_vector.append(memnew(TileSetPluginAtlasNavigation)); - tile_set_plugins_vector.append(memnew(TileSetPluginScenesCollections)); } TileSet::~TileSet() { @@ -2288,9 +2852,6 @@ TileSet::~TileSet() { while (!source_ids.is_empty()) { remove_source(source_ids[0]); } - for (int i = 0; i < tile_set_plugins_vector.size(); i++) { - memdelete(tile_set_plugins_vector[i]); - } } /////////////////////////////// TileSetSource ////////////////////////////////////// @@ -2299,6 +2860,18 @@ void TileSetSource::set_tile_set(const TileSet *p_tile_set) { tile_set = p_tile_set; } +void TileSetSource::_bind_methods() { + // Base tiles + ClassDB::bind_method(D_METHOD("get_tiles_count"), &TileSetSource::get_tiles_count); + ClassDB::bind_method(D_METHOD("get_tile_id", "index"), &TileSetSource::get_tile_id); + ClassDB::bind_method(D_METHOD("has_tile", "atlas_coords"), &TileSetSource::has_tile); + + // Alternative tiles + ClassDB::bind_method(D_METHOD("get_alternative_tiles_count", "atlas_coords"), &TileSetSource::get_alternative_tiles_count); + ClassDB::bind_method(D_METHOD("get_alternative_tile_id", "atlas_coords", "index"), &TileSetSource::get_alternative_tile_id); + ClassDB::bind_method(D_METHOD("has_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetSource::has_alternative_tile); +} + /////////////////////////////// TileSetAtlasSource ////////////////////////////////////// void TileSetAtlasSource::set_tile_set(const TileSet *p_tile_set) { @@ -2321,6 +2894,150 @@ void TileSetAtlasSource::notify_tile_data_properties_should_change() { } } +void TileSetAtlasSource::add_occlusion_layer(int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->add_occlusion_layer(p_to_pos); + } + } +} + +void TileSetAtlasSource::move_occlusion_layer(int p_from_index, int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->move_occlusion_layer(p_from_index, p_to_pos); + } + } +} + +void TileSetAtlasSource::remove_occlusion_layer(int p_index) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->remove_occlusion_layer(p_index); + } + } +} + +void TileSetAtlasSource::add_physics_layer(int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->add_physics_layer(p_to_pos); + } + } +} + +void TileSetAtlasSource::move_physics_layer(int p_from_index, int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->move_physics_layer(p_from_index, p_to_pos); + } + } +} + +void TileSetAtlasSource::remove_physics_layer(int p_index) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->remove_physics_layer(p_index); + } + } +} + +void TileSetAtlasSource::add_terrain_set(int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->add_terrain_set(p_to_pos); + } + } +} + +void TileSetAtlasSource::move_terrain_set(int p_from_index, int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->move_terrain_set(p_from_index, p_to_pos); + } + } +} + +void TileSetAtlasSource::remove_terrain_set(int p_index) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->remove_terrain_set(p_index); + } + } +} + +void TileSetAtlasSource::add_terrain(int p_terrain_set, int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->add_terrain(p_terrain_set, p_to_pos); + } + } +} + +void TileSetAtlasSource::move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->move_terrain(p_terrain_set, p_from_index, p_to_pos); + } + } +} + +void TileSetAtlasSource::remove_terrain(int p_terrain_set, int p_index) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->remove_terrain(p_terrain_set, p_index); + } + } +} + +void TileSetAtlasSource::add_navigation_layer(int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->add_navigation_layer(p_to_pos); + } + } +} + +void TileSetAtlasSource::move_navigation_layer(int p_from_index, int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->move_navigation_layer(p_from_index, p_to_pos); + } + } +} + +void TileSetAtlasSource::remove_navigation_layer(int p_index) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->remove_navigation_layer(p_index); + } + } +} + +void TileSetAtlasSource::add_custom_data_layer(int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->add_custom_data_layer(p_to_pos); + } + } +} + +void TileSetAtlasSource::move_custom_data_layer(int p_from_index, int p_to_pos) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->move_custom_data_layer(p_from_index, p_to_pos); + } + } +} + +void TileSetAtlasSource::remove_custom_data_layer(int p_index) { + for (KeyValue<Vector2i, TileAlternativesData> E_tile : tiles) { + for (KeyValue<int, TileData *> E_alternative : E_tile.value.alternatives) { + E_alternative.value->remove_custom_data_layer(p_index); + } + } +} + void TileSetAtlasSource::reset_state() { // Reset all TileData. for (Map<Vector2i, TileAlternativesData>::Element *E_tile = tiles.front(); E_tile; E_tile = E_tile->next()) { @@ -2516,23 +3233,22 @@ void TileSetAtlasSource::_get_property_list(List<PropertyInfo> *p_list) const { // Get the alternative tile's properties and append them to the list of properties. List<PropertyInfo> alternative_property_list; E_alternative->get()->get_property_list(&alternative_property_list); - for (List<PropertyInfo>::Element *E_property = alternative_property_list.front(); E_property; E_property = E_property->next()) { - property_info = E_property->get(); + for (PropertyInfo &alternative_property_info : alternative_property_list) { bool valid; - Variant default_value = ClassDB::class_get_default_property_value("TileData", property_info.name, &valid); - Variant value = E_alternative->get()->get(property_info.name); + Variant default_value = ClassDB::class_get_default_property_value("TileData", alternative_property_info.name, &valid); + Variant value = E_alternative->get()->get(alternative_property_info.name); if (valid && value == default_value) { property_info.usage ^= PROPERTY_USAGE_STORAGE; } - property_info.name = vformat("%s/%s", vformat("%d", E_alternative->key()), property_info.name); - tile_property_list.push_back(property_info); + alternative_property_info.name = vformat("%s/%s", vformat("%d", E_alternative->key()), alternative_property_info.name); + tile_property_list.push_back(alternative_property_info); } } // Add all alternative. - for (List<PropertyInfo>::Element *E_property = tile_property_list.front(); E_property; E_property = E_property->next()) { - E_property->get().name = vformat("%s/%s", vformat("%d:%d", E_tile->key().x, E_tile->key().y), E_property->get().name); - p_list->push_back(E_property->get()); + for (PropertyInfo &tile_property_info : tile_property_list) { + tile_property_info.name = vformat("%s/%s", vformat("%d:%d", E_tile->key().x, E_tile->key().y), tile_property_info.name); + p_list->push_back(tile_property_info); } } } @@ -2569,7 +3285,7 @@ void TileSetAtlasSource::create_tile(const Vector2i p_atlas_coords, const Vector } } - emit_signal("changed"); + emit_signal(SNAME("changed")); } void TileSetAtlasSource::remove_tile(Vector2i p_atlas_coords) { @@ -2595,7 +3311,7 @@ void TileSetAtlasSource::remove_tile(Vector2i p_atlas_coords) { tiles_ids.erase(p_atlas_coords); tiles_ids.sort(); - emit_signal("changed"); + emit_signal(SNAME("changed")); } bool TileSetAtlasSource::has_tile(Vector2i p_atlas_coords) const { @@ -2723,7 +3439,7 @@ void TileSetAtlasSource::move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_ } } - emit_signal("changed"); + emit_signal(SNAME("changed")); } bool TileSetAtlasSource::has_tiles_outside_texture() { @@ -2755,8 +3471,8 @@ void TileSetAtlasSource::clear_tiles_outside_texture() { } int TileSetAtlasSource::create_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_id_override) { - ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), -1, vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); - ERR_FAIL_COND_V_MSG(p_alternative_id_override >= 0 && tiles[p_atlas_coords].alternatives.has(p_alternative_id_override), -1, vformat("Cannot create alternative tile. Another alternative exists with id %d.", p_alternative_id_override)); + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), TileSetSource::INVALID_TILE_ALTERNATIVE, vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + ERR_FAIL_COND_V_MSG(p_alternative_id_override >= 0 && tiles[p_atlas_coords].alternatives.has(p_alternative_id_override), TileSetSource::INVALID_TILE_ALTERNATIVE, vformat("Cannot create alternative tile. Another alternative exists with id %d.", p_alternative_id_override)); int new_alternative_id = p_alternative_id_override >= 0 ? p_alternative_id_override : tiles[p_atlas_coords].next_alternative_id; @@ -2768,7 +3484,7 @@ int TileSetAtlasSource::create_alternative_tile(const Vector2i p_atlas_coords, i tiles[p_atlas_coords].alternatives_ids.sort(); _compute_next_alternative_id(p_atlas_coords); - emit_signal("changed"); + emit_signal(SNAME("changed")); return new_alternative_id; } @@ -2783,7 +3499,7 @@ void TileSetAtlasSource::remove_alternative_tile(const Vector2i p_atlas_coords, tiles[p_atlas_coords].alternatives_ids.erase(p_alternative_tile); tiles[p_atlas_coords].alternatives_ids.sort(); - emit_signal("changed"); + emit_signal(SNAME("changed")); } void TileSetAtlasSource::set_alternative_tile_id(const Vector2i p_atlas_coords, int p_alternative_tile, int p_new_id) { @@ -2800,7 +3516,7 @@ void TileSetAtlasSource::set_alternative_tile_id(const Vector2i p_atlas_coords, tiles[p_atlas_coords].alternatives_ids.erase(p_alternative_tile); tiles[p_atlas_coords].alternatives_ids.sort(); - emit_signal("changed"); + emit_signal(SNAME("changed")); } bool TileSetAtlasSource::has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const { @@ -2809,7 +3525,7 @@ bool TileSetAtlasSource::has_alternative_tile(const Vector2i p_atlas_coords, int } int TileSetAtlasSource::get_next_alternative_tile_id(const Vector2i p_atlas_coords) const { - ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), -1, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords))); + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), TileSetSource::INVALID_TILE_ALTERNATIVE, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords))); return tiles[p_atlas_coords].next_alternative_id; } @@ -2819,8 +3535,8 @@ int TileSetAtlasSource::get_alternative_tiles_count(const Vector2i p_atlas_coord } int TileSetAtlasSource::get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const { - ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), -1, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords))); - ERR_FAIL_INDEX_V(p_index, tiles[p_atlas_coords].alternatives_ids.size(), -1); + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), TileSetSource::INVALID_TILE_ALTERNATIVE, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords))); + ERR_FAIL_INDEX_V(p_index, tiles[p_atlas_coords].alternatives_ids.size(), TileSetSource::INVALID_TILE_ALTERNATIVE); return tiles[p_atlas_coords].alternatives_ids[p_index]; } @@ -2845,32 +3561,24 @@ void TileSetAtlasSource::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_NOEDITOR), "set_texture", "get_texture"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "margins", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_margins", "get_margins"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "separation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_separation", "get_separation"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "tile_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_texture_region_size", "get_texture_region_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "texture_region_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_texture_region_size", "get_texture_region_size"); // Base tiles ClassDB::bind_method(D_METHOD("create_tile", "atlas_coords", "size"), &TileSetAtlasSource::create_tile, DEFVAL(Vector2i(1, 1))); ClassDB::bind_method(D_METHOD("remove_tile", "atlas_coords"), &TileSetAtlasSource::remove_tile); // Remove a tile. If p_tile_key.alternative_tile if different from 0, remove the alternative - ClassDB::bind_method(D_METHOD("has_tile", "atlas_coords"), &TileSetAtlasSource::has_tile); ClassDB::bind_method(D_METHOD("can_move_tile_in_atlas", "atlas_coords", "new_atlas_coords", "new_size"), &TileSetAtlasSource::can_move_tile_in_atlas, DEFVAL(INVALID_ATLAS_COORDS), DEFVAL(Vector2i(-1, -1))); ClassDB::bind_method(D_METHOD("move_tile_in_atlas", "atlas_coords", "new_atlas_coords", "new_size"), &TileSetAtlasSource::move_tile_in_atlas, DEFVAL(INVALID_ATLAS_COORDS), DEFVAL(Vector2i(-1, -1))); ClassDB::bind_method(D_METHOD("get_tile_size_in_atlas", "atlas_coords"), &TileSetAtlasSource::get_tile_size_in_atlas); - ClassDB::bind_method(D_METHOD("get_tiles_count"), &TileSetAtlasSource::get_tiles_count); - ClassDB::bind_method(D_METHOD("get_tile_id", "index"), &TileSetAtlasSource::get_tile_id); - ClassDB::bind_method(D_METHOD("get_tile_at_coords", "atlas_coords"), &TileSetAtlasSource::get_tile_at_coords); // Alternative tiles - ClassDB::bind_method(D_METHOD("create_alternative_tile", "atlas_coords", "alternative_id_override"), &TileSetAtlasSource::create_alternative_tile, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("create_alternative_tile", "atlas_coords", "alternative_id_override"), &TileSetAtlasSource::create_alternative_tile, DEFVAL(INVALID_TILE_ALTERNATIVE)); ClassDB::bind_method(D_METHOD("remove_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetAtlasSource::remove_alternative_tile); ClassDB::bind_method(D_METHOD("set_alternative_tile_id", "atlas_coords", "alternative_tile", "new_id"), &TileSetAtlasSource::set_alternative_tile_id); - ClassDB::bind_method(D_METHOD("has_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetAtlasSource::has_alternative_tile); ClassDB::bind_method(D_METHOD("get_next_alternative_tile_id", "atlas_coords"), &TileSetAtlasSource::get_next_alternative_tile_id); - ClassDB::bind_method(D_METHOD("get_alternative_tiles_count", "atlas_coords"), &TileSetAtlasSource::get_alternative_tiles_count); - ClassDB::bind_method(D_METHOD("get_alternative_tile_id", "atlas_coords", "index"), &TileSetAtlasSource::get_alternative_tile_id); - - ClassDB::bind_method(D_METHOD("get_tile_data", "atlas_coords", "index"), &TileSetAtlasSource::get_tile_data); + ClassDB::bind_method(D_METHOD("get_tile_data", "atlas_coords", "alternative_tile"), &TileSetAtlasSource::get_tile_data); // Helpers. ClassDB::bind_method(D_METHOD("get_atlas_grid_size"), &TileSetAtlasSource::get_atlas_grid_size); @@ -2948,7 +3656,7 @@ bool TileSetScenesCollectionSource::has_alternative_tile(const Vector2i p_atlas_ } int TileSetScenesCollectionSource::create_scene_tile(Ref<PackedScene> p_packed_scene, int p_id_override) { - ERR_FAIL_COND_V_MSG(p_id_override >= 0 && scenes.has(p_id_override), -1, vformat("Cannot create scene tile. Another scene tile exists with id %d.", p_id_override)); + ERR_FAIL_COND_V_MSG(p_id_override >= 0 && scenes.has(p_id_override), INVALID_TILE_ALTERNATIVE, vformat("Cannot create scene tile. Another scene tile exists with id %d.", p_id_override)); int new_scene_id = p_id_override >= 0 ? p_id_override : next_scene_id; @@ -2958,7 +3666,7 @@ int TileSetScenesCollectionSource::create_scene_tile(Ref<PackedScene> p_packed_s set_scene_tile_scene(new_scene_id, p_packed_scene); _compute_next_alternative_id(); - emit_signal("changed"); + emit_signal(SNAME("changed")); return new_scene_id; } @@ -2978,7 +3686,7 @@ void TileSetScenesCollectionSource::set_scene_tile_id(int p_id, int p_new_id) { scenes.erase(p_id); scenes_ids.erase(p_id); - emit_signal("changed"); + emit_signal(SNAME("changed")); } void TileSetScenesCollectionSource::set_scene_tile_scene(int p_id, Ref<PackedScene> p_packed_scene) { @@ -2997,7 +3705,7 @@ void TileSetScenesCollectionSource::set_scene_tile_scene(int p_id, Ref<PackedSce } else { scenes[p_id].scene = Ref<PackedScene>(); } - emit_signal("changed"); + emit_signal(SNAME("changed")); } Ref<PackedScene> TileSetScenesCollectionSource::get_scene_tile_scene(int p_id) const { @@ -3010,7 +3718,7 @@ void TileSetScenesCollectionSource::set_scene_tile_display_placeholder(int p_id, scenes[p_id].display_placeholder = p_display_placeholder; - emit_signal("changed"); + emit_signal(SNAME("changed")); } bool TileSetScenesCollectionSource::get_scene_tile_display_placeholder(int p_id) const { @@ -3023,7 +3731,7 @@ void TileSetScenesCollectionSource::remove_scene_tile(int p_id) { scenes.erase(p_id); scenes_ids.erase(p_id); - emit_signal("changed"); + emit_signal(SNAME("changed")); } int TileSetScenesCollectionSource::get_next_scene_tile_id() const { @@ -3083,20 +3791,10 @@ void TileSetScenesCollectionSource::_get_property_list(List<PropertyInfo> *p_lis } void TileSetScenesCollectionSource::_bind_methods() { - // Base tiles - ClassDB::bind_method(D_METHOD("get_tiles_count"), &TileSetScenesCollectionSource::get_tiles_count); - ClassDB::bind_method(D_METHOD("get_tile_id", "index"), &TileSetScenesCollectionSource::get_tile_id); - ClassDB::bind_method(D_METHOD("has_tile", "atlas_coords"), &TileSetScenesCollectionSource::has_tile); - - // Alternative tiles - ClassDB::bind_method(D_METHOD("get_alternative_tiles_count", "atlas_coords"), &TileSetScenesCollectionSource::get_alternative_tiles_count); - ClassDB::bind_method(D_METHOD("get_alternative_tile_id", "atlas_coords", "index"), &TileSetScenesCollectionSource::get_alternative_tile_id); - ClassDB::bind_method(D_METHOD("has_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetScenesCollectionSource::has_alternative_tile); - ClassDB::bind_method(D_METHOD("get_scene_tiles_count"), &TileSetScenesCollectionSource::get_scene_tiles_count); ClassDB::bind_method(D_METHOD("get_scene_tile_id", "index"), &TileSetScenesCollectionSource::get_scene_tile_id); ClassDB::bind_method(D_METHOD("has_scene_tile_id", "id"), &TileSetScenesCollectionSource::has_scene_tile_id); - ClassDB::bind_method(D_METHOD("create_scene_tile", "packed_scene", "id_override"), &TileSetScenesCollectionSource::create_scene_tile, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("create_scene_tile", "packed_scene", "id_override"), &TileSetScenesCollectionSource::create_scene_tile, DEFVAL(INVALID_TILE_ALTERNATIVE)); ClassDB::bind_method(D_METHOD("set_scene_tile_id", "id", "new_id"), &TileSetScenesCollectionSource::set_scene_tile_id); ClassDB::bind_method(D_METHOD("set_scene_tile_scene", "id", "packed_scene"), &TileSetScenesCollectionSource::set_scene_tile_scene); ClassDB::bind_method(D_METHOD("get_scene_tile_scene", "id"), &TileSetScenesCollectionSource::get_scene_tile_scene); @@ -3144,7 +3842,156 @@ void TileData::notify_tile_data_properties_should_change() { } notify_property_list_changed(); - emit_signal("changed"); + emit_signal(SNAME("changed")); +} + +void TileData::add_occlusion_layer(int p_to_pos) { + if (p_to_pos < 0) { + p_to_pos = occluders.size(); + } + ERR_FAIL_INDEX(p_to_pos, occluders.size() + 1); + occluders.insert(p_to_pos, Ref<OccluderPolygon2D>()); +} + +void TileData::move_occlusion_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, occluders.size()); + ERR_FAIL_INDEX(p_to_pos, occluders.size() + 1); + occluders.insert(p_to_pos, occluders[p_from_index]); + occluders.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); +} + +void TileData::remove_occlusion_layer(int p_index) { + ERR_FAIL_INDEX(p_index, occluders.size()); + occluders.remove(p_index); +} + +void TileData::add_physics_layer(int p_to_pos) { + if (p_to_pos < 0) { + p_to_pos = physics.size(); + } + ERR_FAIL_INDEX(p_to_pos, physics.size() + 1); + physics.insert(p_to_pos, PhysicsLayerTileData()); +} + +void TileData::move_physics_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, physics.size()); + ERR_FAIL_INDEX(p_to_pos, physics.size() + 1); + physics.insert(p_to_pos, physics[p_from_index]); + physics.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); +} + +void TileData::remove_physics_layer(int p_index) { + ERR_FAIL_INDEX(p_index, physics.size()); + physics.remove(p_index); +} + +void TileData::add_terrain_set(int p_to_pos) { + if (p_to_pos >= 0 && p_to_pos <= terrain_set) { + terrain_set += 1; + } +} + +void TileData::move_terrain_set(int p_from_index, int p_to_pos) { + if (p_from_index == terrain_set) { + terrain_set = (p_from_index < p_to_pos) ? p_to_pos - 1 : p_to_pos; + } else { + if (p_from_index < terrain_set) { + terrain_set -= 1; + } + if (p_to_pos <= terrain_set) { + terrain_set += 1; + } + } +} + +void TileData::remove_terrain_set(int p_index) { + if (p_index == terrain_set) { + terrain_set = -1; + for (int i = 0; i < 16; i++) { + terrain_peering_bits[i] = -1; + } + } else if (terrain_set > p_index) { + terrain_set -= 1; + } +} + +void TileData::add_terrain(int p_terrain_set, int p_to_pos) { + if (terrain_set == p_terrain_set) { + for (int i = 0; i < 16; i++) { + if (p_to_pos >= 0 && p_to_pos <= terrain_peering_bits[i]) { + terrain_peering_bits[i] += 1; + } + } + } +} + +void TileData::move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) { + if (terrain_set == p_terrain_set) { + for (int i = 0; i < 16; i++) { + if (p_from_index == terrain_peering_bits[i]) { + terrain_peering_bits[i] = (p_from_index < p_to_pos) ? p_to_pos - 1 : p_to_pos; + } else { + if (p_from_index < terrain_peering_bits[i]) { + terrain_peering_bits[i] -= 1; + } + if (p_to_pos <= terrain_peering_bits[i]) { + terrain_peering_bits[i] += 1; + } + } + } + } +} + +void TileData::remove_terrain(int p_terrain_set, int p_index) { + if (terrain_set == p_terrain_set) { + for (int i = 0; i < 16; i++) { + if (terrain_peering_bits[i] == p_index) { + terrain_peering_bits[i] = -1; + } else if (terrain_peering_bits[i] > p_index) { + terrain_peering_bits[i] -= 1; + } + } + } +} + +void TileData::add_navigation_layer(int p_to_pos) { + if (p_to_pos < 0) { + p_to_pos = navigation.size(); + } + ERR_FAIL_INDEX(p_to_pos, navigation.size() + 1); + navigation.insert(p_to_pos, Ref<NavigationPolygon>()); +} + +void TileData::move_navigation_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, navigation.size()); + ERR_FAIL_INDEX(p_to_pos, navigation.size() + 1); + navigation.insert(p_to_pos, navigation[p_from_index]); + navigation.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); +} + +void TileData::remove_navigation_layer(int p_index) { + ERR_FAIL_INDEX(p_index, navigation.size()); + navigation.remove(p_index); +} + +void TileData::add_custom_data_layer(int p_to_pos) { + if (p_to_pos < 0) { + p_to_pos = custom_data.size(); + } + ERR_FAIL_INDEX(p_to_pos, custom_data.size() + 1); + custom_data.insert(p_to_pos, Variant()); +} + +void TileData::move_custom_data_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, custom_data.size()); + ERR_FAIL_INDEX(p_to_pos, custom_data.size() + 1); + custom_data.insert(p_to_pos, navigation[p_from_index]); + custom_data.remove(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); +} + +void TileData::remove_custom_data_layer(int p_index) { + ERR_FAIL_INDEX(p_index, custom_data.size()); + custom_data.remove(p_index); } void TileData::reset_state() { @@ -3166,7 +4013,7 @@ bool TileData::is_allowing_transform() const { void TileData::set_flip_h(bool p_flip_h) { ERR_FAIL_COND_MSG(!allow_transform && p_flip_h, "Transform is only allowed for alternative tiles (with its alternative_id != 0)"); flip_h = p_flip_h; - emit_signal("changed"); + emit_signal(SNAME("changed")); } bool TileData::get_flip_h() const { return flip_h; @@ -3175,7 +4022,7 @@ bool TileData::get_flip_h() const { void TileData::set_flip_v(bool p_flip_v) { ERR_FAIL_COND_MSG(!allow_transform && p_flip_v, "Transform is only allowed for alternative tiles (with its alternative_id != 0)"); flip_v = p_flip_v; - emit_signal("changed"); + emit_signal(SNAME("changed")); } bool TileData::get_flip_v() const { @@ -3185,7 +4032,7 @@ bool TileData::get_flip_v() const { void TileData::set_transpose(bool p_transpose) { ERR_FAIL_COND_MSG(!allow_transform && p_transpose, "Transform is only allowed for alternative tiles (with its alternative_id != 0)"); transpose = p_transpose; - emit_signal("changed"); + emit_signal(SNAME("changed")); } bool TileData::get_transpose() const { return transpose; @@ -3193,24 +4040,24 @@ bool TileData::get_transpose() const { void TileData::set_texture_offset(Vector2i p_texture_offset) { tex_offset = p_texture_offset; - emit_signal("changed"); + emit_signal(SNAME("changed")); } Vector2i TileData::get_texture_offset() const { return tex_offset; } -void TileData::tile_set_material(Ref<ShaderMaterial> p_material) { +void TileData::set_material(Ref<ShaderMaterial> p_material) { material = p_material; - emit_signal("changed"); + emit_signal(SNAME("changed")); } -Ref<ShaderMaterial> TileData::tile_get_material() const { +Ref<ShaderMaterial> TileData::get_material() const { return material; } void TileData::set_modulate(Color p_modulate) { modulate = p_modulate; - emit_signal("changed"); + emit_signal(SNAME("changed")); } Color TileData::get_modulate() const { return modulate; @@ -3218,7 +4065,7 @@ Color TileData::get_modulate() const { void TileData::set_z_index(int p_z_index) { z_index = p_z_index; - emit_signal("changed"); + emit_signal(SNAME("changed")); } int TileData::get_z_index() const { return z_index; @@ -3226,7 +4073,7 @@ int TileData::get_z_index() const { void TileData::set_y_sort_origin(int p_y_sort_origin) { y_sort_origin = p_y_sort_origin; - emit_signal("changed"); + emit_signal(SNAME("changed")); } int TileData::get_y_sort_origin() const { return y_sort_origin; @@ -3235,7 +4082,7 @@ int TileData::get_y_sort_origin() const { void TileData::set_occluder(int p_layer_id, Ref<OccluderPolygon2D> p_occluder_polygon) { ERR_FAIL_INDEX(p_layer_id, occluders.size()); occluders.write[p_layer_id] = p_occluder_polygon; - emit_signal("changed"); + emit_signal(SNAME("changed")); } Ref<OccluderPolygon2D> TileData::get_occluder(int p_layer_id) const { @@ -3254,20 +4101,20 @@ void TileData::set_collision_polygons_count(int p_layer_id, int p_polygons_count ERR_FAIL_COND(p_polygons_count < 0); physics.write[p_layer_id].polygons.resize(p_polygons_count); notify_property_list_changed(); - emit_signal("changed"); + emit_signal(SNAME("changed")); } void TileData::add_collision_polygon(int p_layer_id) { ERR_FAIL_INDEX(p_layer_id, physics.size()); physics.write[p_layer_id].polygons.push_back(PhysicsLayerTileData::PolygonShapeTileData()); - emit_signal("changed"); + emit_signal(SNAME("changed")); } void TileData::remove_collision_polygon(int p_layer_id, int p_polygon_index) { ERR_FAIL_INDEX(p_layer_id, physics.size()); ERR_FAIL_INDEX(p_polygon_index, physics[p_layer_id].polygons.size()); physics.write[p_layer_id].polygons.remove(p_polygon_index); - emit_signal("changed"); + emit_signal(SNAME("changed")); } void TileData::set_collision_polygon_points(int p_layer_id, int p_polygon_index, Vector<Vector2> p_polygon) { @@ -3291,7 +4138,7 @@ void TileData::set_collision_polygon_points(int p_layer_id, int p_polygon_index, } } physics.write[p_layer_id].polygons.write[p_polygon_index].polygon = p_polygon; - emit_signal("changed"); + emit_signal(SNAME("changed")); } Vector<Vector2> TileData::get_collision_polygon_points(int p_layer_id, int p_polygon_index) const { @@ -3304,7 +4151,7 @@ void TileData::set_collision_polygon_one_way(int p_layer_id, int p_polygon_index ERR_FAIL_INDEX(p_layer_id, physics.size()); ERR_FAIL_INDEX(p_polygon_index, physics[p_layer_id].polygons.size()); physics.write[p_layer_id].polygons.write[p_polygon_index].one_way = p_one_way; - emit_signal("changed"); + emit_signal(SNAME("changed")); } bool TileData::is_collision_polygon_one_way(int p_layer_id, int p_polygon_index) const { @@ -3317,7 +4164,7 @@ void TileData::set_collision_polygon_one_way_margin(int p_layer_id, int p_polygo ERR_FAIL_INDEX(p_layer_id, physics.size()); ERR_FAIL_INDEX(p_polygon_index, physics[p_layer_id].polygons.size()); physics.write[p_layer_id].polygons.write[p_polygon_index].one_way_margin = p_one_way_margin; - emit_signal("changed"); + emit_signal(SNAME("changed")); } float TileData::get_collision_polygon_one_way_margin(int p_layer_id, int p_polygon_index) const { @@ -3353,7 +4200,7 @@ void TileData::set_terrain_set(int p_terrain_set) { } terrain_set = p_terrain_set; notify_property_list_changed(); - emit_signal("changed"); + emit_signal(SNAME("changed")); } int TileData::get_terrain_set() const { @@ -3361,6 +4208,7 @@ int TileData::get_terrain_set() const { } void TileData::set_peering_bit_terrain(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) { @@ -3368,7 +4216,7 @@ void TileData::set_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit, int ERR_FAIL_COND(!is_valid_peering_bit_terrain(p_peering_bit)); } terrain_peering_bits[p_peering_bit] = p_terrain_index; - emit_signal("changed"); + emit_signal(SNAME("changed")); } int TileData::get_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit) const { @@ -3386,7 +4234,7 @@ bool TileData::is_valid_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit) void TileData::set_navigation_polygon(int p_layer_id, Ref<NavigationPolygon> p_navigation_polygon) { ERR_FAIL_INDEX(p_layer_id, navigation.size()); navigation.write[p_layer_id] = p_navigation_polygon; - emit_signal("changed"); + emit_signal(SNAME("changed")); } Ref<NavigationPolygon> TileData::get_navigation_polygon(int p_layer_id) const { @@ -3398,7 +4246,7 @@ Ref<NavigationPolygon> TileData::get_navigation_polygon(int p_layer_id) const { void TileData::set_probability(float p_probability) { ERR_FAIL_COND(p_probability < 0.0); probability = p_probability; - emit_signal("changed"); + emit_signal(SNAME("changed")); } float TileData::get_probability() const { return probability; @@ -3422,7 +4270,7 @@ Variant TileData::get_custom_data(String p_layer_name) const { void TileData::set_custom_data_by_layer_id(int p_layer_id, Variant p_value) { ERR_FAIL_INDEX(p_layer_id, custom_data.size()); custom_data.write[p_layer_id] = p_value; - emit_signal("changed"); + emit_signal(SNAME("changed")); } Variant TileData::get_custom_data_by_layer_id(int p_layer_id) const { @@ -3719,8 +4567,8 @@ void TileData::_bind_methods() { ClassDB::bind_method(D_METHOD("get_flip_v"), &TileData::get_flip_v); ClassDB::bind_method(D_METHOD("set_transpose", "transpose"), &TileData::set_transpose); ClassDB::bind_method(D_METHOD("get_transpose"), &TileData::get_transpose); - ClassDB::bind_method(D_METHOD("tile_set_material", "material"), &TileData::tile_set_material); - ClassDB::bind_method(D_METHOD("tile_get_material"), &TileData::tile_get_material); + ClassDB::bind_method(D_METHOD("set_material", "material"), &TileData::set_material); + ClassDB::bind_method(D_METHOD("get_material"), &TileData::get_material); ClassDB::bind_method(D_METHOD("set_texture_offset", "texture_offset"), &TileData::set_texture_offset); ClassDB::bind_method(D_METHOD("get_texture_offset"), &TileData::get_texture_offset); ClassDB::bind_method(D_METHOD("set_modulate", "modulate"), &TileData::set_modulate); @@ -3771,6 +4619,7 @@ void TileData::_bind_methods() { 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::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::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"); @@ -3782,842 +4631,3 @@ void TileData::_bind_methods() { ADD_SIGNAL(MethodInfo("changed")); } -/////////////////////////////// TileSetPluginAtlasRendering ////////////////////////////////////// - -void TileSetPluginAtlasRendering::tilemap_notification(TileMap *p_tile_map, int p_what) { - switch (p_what) { - case CanvasItem::NOTIFICATION_VISIBILITY_CHANGED: { - bool visible = p_tile_map->is_visible_in_tree(); - for (Map<Vector2i, TileMapQuadrant>::Element *E_quadrant = p_tile_map->get_quadrant_map().front(); E_quadrant; E_quadrant = E_quadrant->next()) { - TileMapQuadrant &q = E_quadrant->get(); - - // Update occluders transform. - for (Map<Vector2i, Vector2i, TileMapQuadrant::CoordsWorldComparator>::Element *E_cell = q.world_to_map.front(); E_cell; E_cell = E_cell->next()) { - Transform2D xform; - xform.set_origin(E_cell->key()); - for (List<RID>::Element *E_occluder_id = q.occluders.front(); E_occluder_id; E_occluder_id = E_occluder_id->next()) { - RS::get_singleton()->canvas_light_occluder_set_enabled(E_occluder_id->get(), visible); - } - } - } - } break; - case CanvasItem::NOTIFICATION_TRANSFORM_CHANGED: { - if (!p_tile_map->is_inside_tree()) { - return; - } - - for (Map<Vector2i, TileMapQuadrant>::Element *E_quadrant = p_tile_map->get_quadrant_map().front(); E_quadrant; E_quadrant = E_quadrant->next()) { - TileMapQuadrant &q = E_quadrant->get(); - - // Update occluders transform. - for (Map<Vector2i, Vector2i, TileMapQuadrant::CoordsWorldComparator>::Element *E_cell = q.world_to_map.front(); E_cell; E_cell = E_cell->next()) { - Transform2D xform; - xform.set_origin(E_cell->key()); - for (List<RID>::Element *E_occluder_id = q.occluders.front(); E_occluder_id; E_occluder_id = E_occluder_id->next()) { - RS::get_singleton()->canvas_light_occluder_set_transform(E_occluder_id->get(), p_tile_map->get_global_transform() * xform); - } - } - } - } break; - case CanvasItem::NOTIFICATION_DRAW: { - Ref<TileSet> tile_set = p_tile_map->get_tileset(); - if (tile_set.is_valid() || p_tile_map->is_y_sort_enabled()) { - RenderingServer::get_singleton()->canvas_item_set_sort_children_by_y(p_tile_map->get_canvas_item(), tile_set->is_y_sorting() || p_tile_map->is_y_sort_enabled()); - } - } break; - } -} - -void TileSetPluginAtlasRendering::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, Color p_modulation) { - ERR_FAIL_COND(!p_tile_set.is_valid()); - ERR_FAIL_COND(!p_tile_set->has_source(p_atlas_source_id)); - ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_tile(p_atlas_coords)); - ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_alternative_tile(p_atlas_coords, p_alternative_tile)); - - TileSetSource *source = *p_tile_set->get_source(p_atlas_source_id); - TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); - if (atlas_source) { - // Get the texture. - Ref<Texture2D> tex = atlas_source->get_texture(); - if (!tex.is_valid()) { - return; - } - - // Check if we are in the texture, return otherwise. - Vector2i grid_size = atlas_source->get_atlas_grid_size(); - if (p_atlas_coords.x >= grid_size.x || p_atlas_coords.y >= grid_size.y) { - return; - } - - // Get tile data. - TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile)); - - // Compute the offset - Rect2i source_rect = atlas_source->get_tile_texture_region(p_atlas_coords); - Vector2i tile_offset = atlas_source->get_tile_effective_texture_offset(p_atlas_coords, p_alternative_tile); - - // Compute the destination rectangle in the CanvasItem. - Rect2 dest_rect; - dest_rect.size = source_rect.size; - dest_rect.size.x += fp_adjust; - dest_rect.size.y += fp_adjust; - - bool transpose = tile_data->get_transpose(); - if (transpose) { - dest_rect.position = (p_position - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset); - } else { - dest_rect.position = (p_position - dest_rect.size / 2 - tile_offset); - } - - if (tile_data->get_flip_h()) { - dest_rect.size.x = -dest_rect.size.x; - } - - if (tile_data->get_flip_v()) { - dest_rect.size.y = -dest_rect.size.y; - } - - // Get the tile modulation. - Color modulate = tile_data->get_modulate(); - modulate = Color(modulate.r * p_modulation.r, modulate.g * p_modulation.g, modulate.b * p_modulation.b, modulate.a * p_modulation.a); - - // Draw the tile. - tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); - } -} - -void TileSetPluginAtlasRendering::update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { - ERR_FAIL_COND(!p_tile_map); - ERR_FAIL_COND(!p_tile_map->is_inside_tree()); - Ref<TileSet> tile_set = p_tile_map->get_tileset(); - ERR_FAIL_COND(!tile_set.is_valid()); - - bool visible = p_tile_map->is_visible_in_tree(); - - SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); - while (q_list_element) { - TileMapQuadrant &q = *q_list_element->self(); - - RenderingServer *rs = RenderingServer::get_singleton(); - - // Free the canvas items. - for (List<RID>::Element *E = q.canvas_items.front(); E; E = E->next()) { - rs->free(E->get()); - } - q.canvas_items.clear(); - - // Free the occluders. - for (List<RID>::Element *E = q.occluders.front(); E; E = E->next()) { - rs->free(E->get()); - } - q.occluders.clear(); - - // Those allow to group cell per material or z-index. - Ref<ShaderMaterial> prev_material; - int prev_z_index = 0; - RID prev_canvas_item; - - // Iterate over the cells of the quadrant. - for (Map<Vector2i, Vector2i, TileMapQuadrant::CoordsWorldComparator>::Element *E_cell = q.world_to_map.front(); E_cell; E_cell = E_cell->next()) { - TileMapCell c = p_tile_map->get_cell(E_cell->value()); - - TileSetSource *source; - if (tile_set->has_source(c.source_id)) { - source = *tile_set->get_source(c.source_id); - - if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { - continue; - } - - TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); - if (atlas_source) { - // Get the tile data. - TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); - Ref<ShaderMaterial> mat = tile_data->tile_get_material(); - int z_index = tile_data->get_z_index(); - - // Quandrant pos. - Vector2 position = p_tile_map->map_to_world(q.coords * p_tile_map->get_effective_quadrant_size()); - if (tile_set->is_y_sorting()) { - // When Y-sorting, the quandrant size is sure to be 1, we can thus offset the CanvasItem. - position.y += tile_data->get_y_sort_origin(); - } - - // --- CanvasItems --- - // Create two canvas items, for rendering and debug. - RID canvas_item; - - // Check if the material or the z_index changed. - if (prev_canvas_item == RID() || prev_material != mat || prev_z_index != z_index) { - // If so, create a new CanvasItem. - canvas_item = rs->canvas_item_create(); - if (mat.is_valid()) { - rs->canvas_item_set_material(canvas_item, mat->get_rid()); - } - rs->canvas_item_set_parent(canvas_item, p_tile_map->get_canvas_item()); - rs->canvas_item_set_use_parent_material(canvas_item, p_tile_map->get_use_parent_material() || p_tile_map->get_material().is_valid()); - - Transform2D xform; - xform.set_origin(position); - rs->canvas_item_set_transform(canvas_item, xform); - - rs->canvas_item_set_light_mask(canvas_item, p_tile_map->get_light_mask()); - rs->canvas_item_set_z_index(canvas_item, z_index); - - rs->canvas_item_set_default_texture_filter(canvas_item, RS::CanvasItemTextureFilter(p_tile_map->CanvasItem::get_texture_filter())); - rs->canvas_item_set_default_texture_repeat(canvas_item, RS::CanvasItemTextureRepeat(p_tile_map->CanvasItem::get_texture_repeat())); - - q.canvas_items.push_back(canvas_item); - - prev_canvas_item = canvas_item; - prev_material = mat; - prev_z_index = z_index; - - } else { - // Keep the same canvas_item to draw on. - canvas_item = prev_canvas_item; - } - - // Drawing the tile in the canvas item. - draw_tile(canvas_item, E_cell->key() - position, tile_set, c.source_id, c.get_atlas_coords(), c.alternative_tile, p_tile_map->get_self_modulate()); - - // --- Occluders --- - for (int i = 0; i < tile_set->get_occlusion_layers_count(); i++) { - Transform2D xform; - xform.set_origin(E_cell->key()); - if (tile_data->get_occluder(i).is_valid()) { - RID occluder_id = rs->canvas_light_occluder_create(); - rs->canvas_light_occluder_set_enabled(occluder_id, visible); - rs->canvas_light_occluder_set_transform(occluder_id, p_tile_map->get_global_transform() * xform); - rs->canvas_light_occluder_set_polygon(occluder_id, tile_data->get_occluder(i)->get_rid()); - rs->canvas_light_occluder_attach_to_canvas(occluder_id, p_tile_map->get_canvas()); - rs->canvas_light_occluder_set_light_mask(occluder_id, tile_set->get_occlusion_layer_light_mask(i)); - q.occluders.push_back(occluder_id); - } - } - } - } - } - - quadrant_order_dirty = true; - q_list_element = q_list_element->next(); - } - - // Reset the drawing indices - if (quadrant_order_dirty) { - int index = -(int64_t)0x80000000; //always must be drawn below children. - - // Sort the quadrants coords per world coordinates - Map<Vector2i, Vector2i, TileMapQuadrant::CoordsWorldComparator> world_to_map; - Map<Vector2i, TileMapQuadrant> quadrant_map = p_tile_map->get_quadrant_map(); - for (Map<Vector2i, TileMapQuadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { - world_to_map[p_tile_map->map_to_world(E->key())] = E->key(); - } - - // Sort the quadrants - for (Map<Vector2i, Vector2i, TileMapQuadrant::CoordsWorldComparator>::Element *E = world_to_map.front(); E; E = E->next()) { - TileMapQuadrant &q = quadrant_map[E->value()]; - for (List<RID>::Element *F = q.canvas_items.front(); F; F = F->next()) { - RS::get_singleton()->canvas_item_set_draw_index(F->get(), index++); - } - } - - quadrant_order_dirty = false; - } -} - -void TileSetPluginAtlasRendering::create_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { - Ref<TileSet> tile_set = p_tile_map->get_tileset(); - ERR_FAIL_COND(!tile_set.is_valid()); - - quadrant_order_dirty = true; -} - -void TileSetPluginAtlasRendering::cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { - // Free the canvas items. - for (List<RID>::Element *E = p_quadrant->canvas_items.front(); E; E = E->next()) { - RenderingServer::get_singleton()->free(E->get()); - } - p_quadrant->canvas_items.clear(); - - // Free the occluders. - for (List<RID>::Element *E = p_quadrant->occluders.front(); E; E = E->next()) { - RenderingServer::get_singleton()->free(E->get()); - } - p_quadrant->occluders.clear(); -} - -void TileSetPluginAtlasRendering::draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { - Ref<TileSet> tile_set = p_tile_map->get_tileset(); - ERR_FAIL_COND(!tile_set.is_valid()); - - if (!Engine::get_singleton()->is_editor_hint()) { - return; - } - - // Draw a placeholder for scenes needing one. - RenderingServer *rs = RenderingServer::get_singleton(); - Vector2 quadrant_pos = p_tile_map->map_to_world(p_quadrant->coords * p_tile_map->get_effective_quadrant_size()); - for (Set<Vector2i>::Element *E_cell = p_quadrant->cells.front(); E_cell; E_cell = E_cell->next()) { - const TileMapCell &c = p_tile_map->get_cell(E_cell->get()); - - TileSetSource *source; - if (tile_set->has_source(c.source_id)) { - source = *tile_set->get_source(c.source_id); - - if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { - continue; - } - - TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); - if (atlas_source) { - Vector2i grid_size = atlas_source->get_atlas_grid_size(); - if (!atlas_source->get_texture().is_valid() || c.get_atlas_coords().x >= grid_size.x || c.get_atlas_coords().y >= grid_size.y) { - // Generate a random color from the hashed values of the tiles. - Array to_hash; - to_hash.push_back(c.source_id); - to_hash.push_back(c.get_atlas_coords()); - to_hash.push_back(c.alternative_tile); - uint32_t hash = RandomPCG(to_hash.hash()).rand(); - - Color color; - color = color.from_hsv( - (float)((hash >> 24) & 0xFF) / 256.0, - Math::lerp(0.5, 1.0, (float)((hash >> 16) & 0xFF) / 256.0), - Math::lerp(0.5, 1.0, (float)((hash >> 8) & 0xFF) / 256.0), - 0.8); - - // Draw a placeholder tile. - Transform2D xform; - xform.set_origin(p_tile_map->map_to_world(E_cell->get()) - 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); - } - } - } - } -} - -/////////////////////////////// TileSetPluginAtlasPhysics ////////////////////////////////////// - -void TileSetPluginAtlasPhysics::tilemap_notification(TileMap *p_tile_map, int p_what) { - switch (p_what) { - case CanvasItem::NOTIFICATION_TRANSFORM_CHANGED: { - // Update the bodies transforms. - if (p_tile_map->is_inside_tree()) { - Map<Vector2i, TileMapQuadrant> quadrant_map = p_tile_map->get_quadrant_map(); - Transform2D global_transform = p_tile_map->get_global_transform(); - - for (Map<Vector2i, TileMapQuadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { - TileMapQuadrant &q = E->get(); - - Transform2D xform; - xform.set_origin(p_tile_map->map_to_world(E->key() * p_tile_map->get_effective_quadrant_size())); - xform = global_transform * xform; - - for (int body_index = 0; body_index < q.bodies.size(); body_index++) { - PhysicsServer2D::get_singleton()->body_set_state(q.bodies[body_index], PhysicsServer2D::BODY_STATE_TRANSFORM, xform); - } - } - } - } break; - } -} - -void TileSetPluginAtlasPhysics::update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { - ERR_FAIL_COND(!p_tile_map); - ERR_FAIL_COND(!p_tile_map->is_inside_tree()); - Ref<TileSet> tile_set = p_tile_map->get_tileset(); - ERR_FAIL_COND(!tile_set.is_valid()); - - Transform2D global_transform = p_tile_map->get_global_transform(); - PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); - - SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); - while (q_list_element) { - TileMapQuadrant &q = *q_list_element->self(); - - Vector2 quadrant_pos = p_tile_map->map_to_world(q.coords * p_tile_map->get_effective_quadrant_size()); - - // Clear shapes. - for (int body_index = 0; body_index < q.bodies.size(); body_index++) { - ps->body_clear_shapes(q.bodies[body_index]); - - // Position the bodies. - Transform2D xform; - xform.set_origin(quadrant_pos); - xform = global_transform * xform; - ps->body_set_state(q.bodies[body_index], PhysicsServer2D::BODY_STATE_TRANSFORM, xform); - } - - for (Set<Vector2i>::Element *E_cell = q.cells.front(); E_cell; E_cell = E_cell->next()) { - TileMapCell c = p_tile_map->get_cell(E_cell->get()); - - TileSetSource *source; - if (tile_set->has_source(c.source_id)) { - source = *tile_set->get_source(c.source_id); - - if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { - continue; - } - - TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); - if (atlas_source) { - TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); - - for (int body_index = 0; body_index < q.bodies.size(); body_index++) { - // Add the shapes again. - for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(body_index); polygon_index++) { - bool one_way_collision = tile_data->is_collision_polygon_one_way(body_index, polygon_index); - float one_way_collision_margin = tile_data->get_collision_polygon_one_way_margin(body_index, polygon_index); - - int shapes_count = tile_data->get_collision_polygon_shapes_count(body_index, polygon_index); - for (int shape_index = 0; shape_index < shapes_count; shape_index++) { - Transform2D xform = Transform2D(); - xform.set_origin(p_tile_map->map_to_world(E_cell->get()) - quadrant_pos); - - // Add decomposed convex shapes. - Ref<ConvexPolygonShape2D> shape = tile_data->get_collision_polygon_shape(body_index, polygon_index, shape_index); - ps->body_add_shape(q.bodies[body_index], shape->get_rid(), xform); - ps->body_set_shape_metadata(q.bodies[body_index], shape_index, E_cell->get()); - ps->body_set_shape_as_one_way_collision(q.bodies[body_index], shape_index, one_way_collision, one_way_collision_margin); - } - } - } - } - } - } - - q_list_element = q_list_element->next(); - } -} - -void TileSetPluginAtlasPhysics::create_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { - Ref<TileSet> tile_set = p_tile_map->get_tileset(); - ERR_FAIL_COND(!tile_set.is_valid()); - - //Get the TileMap's gobla transform. - Transform2D global_transform; - if (p_tile_map->is_inside_tree()) { - global_transform = p_tile_map->get_global_transform(); - } - - // Clear all bodies. - p_quadrant->bodies.clear(); - - // Create the body and set its parameters. - for (int layer_index = 0; layer_index < tile_set->get_physics_layers_count(); layer_index++) { - RID body = PhysicsServer2D::get_singleton()->body_create(); - PhysicsServer2D::get_singleton()->body_set_mode(body, PhysicsServer2D::BODY_MODE_STATIC); - - PhysicsServer2D::get_singleton()->body_attach_object_instance_id(body, p_tile_map->get_instance_id()); - PhysicsServer2D::get_singleton()->body_set_collision_layer(body, tile_set->get_physics_layer_collision_layer(layer_index)); - PhysicsServer2D::get_singleton()->body_set_collision_mask(body, tile_set->get_physics_layer_collision_mask(layer_index)); - - Ref<PhysicsMaterial> physics_material = tile_set->get_physics_layer_physics_material(layer_index); - if (!physics_material.is_valid()) { - PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, 0); - PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, 1); - } else { - PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, physics_material->computed_bounce()); - PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, physics_material->computed_friction()); - } - - if (p_tile_map->is_inside_tree()) { - RID space = p_tile_map->get_world_2d()->get_space(); - PhysicsServer2D::get_singleton()->body_set_space(body, space); - - Transform2D xform; - xform.set_origin(p_tile_map->map_to_world(p_quadrant->coords * p_tile_map->get_effective_quadrant_size())); - xform = global_transform * xform; - PhysicsServer2D::get_singleton()->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); - } - - p_quadrant->bodies.push_back(body); - } -} - -void TileSetPluginAtlasPhysics::cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { - // Remove a quadrant. - for (int body_index = 0; body_index < p_quadrant->bodies.size(); body_index++) { - PhysicsServer2D::get_singleton()->free(p_quadrant->bodies[body_index]); - } - p_quadrant->bodies.clear(); -} - -void TileSetPluginAtlasPhysics::draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { - // Draw the debug collision shapes. - Ref<TileSet> tile_set = p_tile_map->get_tileset(); - ERR_FAIL_COND(!tile_set.is_valid()); - - if (!p_tile_map->get_tree()) { - return; - } - - bool show_collision = false; - switch (p_tile_map->get_collision_visibility_mode()) { - case TileMap::VISIBILITY_MODE_DEFAULT: - show_collision = !Engine::get_singleton()->is_editor_hint() && (p_tile_map->get_tree() && p_tile_map->get_tree()->is_debugging_navigation_hint()); - break; - case TileMap::VISIBILITY_MODE_FORCE_HIDE: - show_collision = false; - break; - case TileMap::VISIBILITY_MODE_FORCE_SHOW: - show_collision = true; - break; - } - if (!show_collision) { - return; - } - - RenderingServer *rs = RenderingServer::get_singleton(); - - Vector2 quadrant_pos = p_tile_map->map_to_world(p_quadrant->coords * p_tile_map->get_effective_quadrant_size()); - - Color debug_collision_color = p_tile_map->get_tree()->get_debug_collisions_color(); - for (Set<Vector2i>::Element *E_cell = p_quadrant->cells.front(); E_cell; E_cell = E_cell->next()) { - TileMapCell c = p_tile_map->get_cell(E_cell->get()); - - Transform2D xform; - xform.set_origin(p_tile_map->map_to_world(E_cell->get()) - quadrant_pos); - rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, xform); - - if (tile_set->has_source(c.source_id)) { - TileSetSource *source = *tile_set->get_source(c.source_id); - - if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { - continue; - } - - TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); - if (atlas_source) { - TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); - - for (int body_index = 0; body_index < p_quadrant->bodies.size(); body_index++) { - for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(body_index); polygon_index++) { - // Draw the debug polygon. - Vector<Vector2> polygon = tile_data->get_collision_polygon_points(body_index, polygon_index); - if (polygon.size() >= 3) { - Vector<Color> color; - color.push_back(debug_collision_color); - rs->canvas_item_add_polygon(p_quadrant->debug_canvas_item, polygon, color); - } - } - } - } - } - rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, Transform2D()); - } -}; - -/////////////////////////////// TileSetPluginAtlasNavigation ////////////////////////////////////// - -void TileSetPluginAtlasNavigation::tilemap_notification(TileMap *p_tile_map, int p_what) { - switch (p_what) { - case CanvasItem::NOTIFICATION_TRANSFORM_CHANGED: { - if (p_tile_map->is_inside_tree()) { - Map<Vector2i, TileMapQuadrant> quadrant_map = p_tile_map->get_quadrant_map(); - Transform2D tilemap_xform = p_tile_map->get_global_transform(); - for (Map<Vector2i, TileMapQuadrant>::Element *E_quadrant = quadrant_map.front(); E_quadrant; E_quadrant = E_quadrant->next()) { - TileMapQuadrant &q = E_quadrant->get(); - for (Map<Vector2i, Vector<RID>>::Element *E_region = q.navigation_regions.front(); E_region; E_region = E_region->next()) { - for (int layer_index = 0; layer_index < E_region->get().size(); layer_index++) { - RID region = E_region->get()[layer_index]; - if (!region.is_valid()) { - continue; - } - Transform2D tile_transform; - tile_transform.set_origin(p_tile_map->map_to_world(E_region->key())); - NavigationServer2D::get_singleton()->region_set_transform(region, tilemap_xform * tile_transform); - } - } - } - } - } break; - } -} - -void TileSetPluginAtlasNavigation::update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { - ERR_FAIL_COND(!p_tile_map); - ERR_FAIL_COND(!p_tile_map->is_inside_tree()); - Ref<TileSet> tile_set = p_tile_map->get_tileset(); - ERR_FAIL_COND(!tile_set.is_valid()); - - // Get colors for debug. - SceneTree *st = SceneTree::get_singleton(); - Color debug_navigation_color; - bool debug_navigation = st && st->is_debugging_navigation_hint(); - if (debug_navigation) { - debug_navigation_color = st->get_debug_navigation_color(); - } - - Transform2D tilemap_xform = p_tile_map->get_global_transform(); - SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); - while (q_list_element) { - TileMapQuadrant &q = *q_list_element->self(); - - // Clear navigation shapes in the quadrant. - for (Map<Vector2i, Vector<RID>>::Element *E = q.navigation_regions.front(); E; E = E->next()) { - for (int i = 0; i < E->get().size(); i++) { - RID region = E->get()[i]; - if (!region.is_valid()) { - continue; - } - NavigationServer2D::get_singleton()->region_set_map(region, RID()); - } - } - 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 = p_tile_map->get_cell(E_cell->get()); - - TileSetSource *source; - if (tile_set->has_source(c.source_id)) { - source = *tile_set->get_source(c.source_id); - - if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { - continue; - } - - TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); - if (atlas_source) { - TileData *tile_data = Object::cast_to<TileData>(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()); - - for (int layer_index = 0; layer_index < tile_set->get_navigation_layers_count(); layer_index++) { - Ref<NavigationPolygon> navpoly; - navpoly = tile_data->get_navigation_polygon(layer_index); - - if (navpoly.is_valid()) { - Transform2D tile_transform; - tile_transform.set_origin(p_tile_map->map_to_world(E_cell->get())); - - RID region = NavigationServer2D::get_singleton()->region_create(); - NavigationServer2D::get_singleton()->region_set_map(region, p_tile_map->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_list_element = q_list_element->next(); - } -} - -void TileSetPluginAtlasNavigation::cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { - // Clear navigation shapes in the quadrant. - for (Map<Vector2i, Vector<RID>>::Element *E = p_quadrant->navigation_regions.front(); E; E = E->next()) { - for (int i = 0; i < E->get().size(); i++) { - RID region = E->get()[i]; - if (!region.is_valid()) { - continue; - } - NavigationServer2D::get_singleton()->free(region); - } - } - p_quadrant->navigation_regions.clear(); -} - -void TileSetPluginAtlasNavigation::draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { - // Draw the debug collision shapes. - Ref<TileSet> tile_set = p_tile_map->get_tileset(); - ERR_FAIL_COND(!tile_set.is_valid()); - - if (!p_tile_map->get_tree()) { - return; - } - - bool show_navigation = false; - switch (p_tile_map->get_navigation_visibility_mode()) { - case TileMap::VISIBILITY_MODE_DEFAULT: - show_navigation = !Engine::get_singleton()->is_editor_hint() && (p_tile_map->get_tree() && p_tile_map->get_tree()->is_debugging_navigation_hint()); - break; - case TileMap::VISIBILITY_MODE_FORCE_HIDE: - show_navigation = false; - break; - case TileMap::VISIBILITY_MODE_FORCE_SHOW: - show_navigation = true; - break; - } - if (!show_navigation) { - return; - } - - RenderingServer *rs = RenderingServer::get_singleton(); - - Color color = p_tile_map->get_tree()->get_debug_navigation_color(); - RandomPCG rand; - - Vector2 quadrant_pos = p_tile_map->map_to_world(p_quadrant->coords * p_tile_map->get_effective_quadrant_size()); - - for (Set<Vector2i>::Element *E_cell = p_quadrant->cells.front(); E_cell; E_cell = E_cell->next()) { - TileMapCell c = p_tile_map->get_cell(E_cell->get()); - - TileSetSource *source; - if (tile_set->has_source(c.source_id)) { - source = *tile_set->get_source(c.source_id); - - if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { - continue; - } - - TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); - if (atlas_source) { - TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); - - Transform2D xform; - xform.set_origin(p_tile_map->map_to_world(E_cell->get()) - 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++) { - Ref<NavigationPolygon> navpoly = tile_data->get_navigation_polygon(layer_index); - if (navpoly.is_valid()) { - PackedVector2Array navigation_polygon_vertices = navpoly->get_vertices(); - - for (int i = 0; i < navpoly->get_polygon_count(); i++) { - // An array of vertices for this polygon. - Vector<int> polygon = navpoly->get_polygon(i); - Vector<Vector2> vertices; - vertices.resize(polygon.size()); - for (int j = 0; j < polygon.size(); j++) { - ERR_FAIL_INDEX(polygon[j], navigation_polygon_vertices.size()); - vertices.write[j] = navigation_polygon_vertices[polygon[j]]; - } - - // Generate the polygon color, slightly randomly modified from the settings one. - Color random_variation_color; - random_variation_color.set_hsv(color.get_h() + rand.random(-1.0, 1.0) * 0.05, color.get_s(), color.get_v() + rand.random(-1.0, 1.0) * 0.1); - random_variation_color.a = color.a; - Vector<Color> colors; - colors.push_back(random_variation_color); - - rs->canvas_item_add_polygon(p_quadrant->debug_canvas_item, vertices, colors); - } - } - } - } - } - } -} - -/////////////////////////////// TileSetPluginScenesCollections ////////////////////////////////////// - -void TileSetPluginScenesCollections::update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { - Ref<TileSet> tile_set = p_tile_map->get_tileset(); - ERR_FAIL_COND(!tile_set.is_valid()); - - SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); - while (q_list_element) { - TileMapQuadrant &q = *q_list_element->self(); - - // Clear the scenes. - for (Map<Vector2i, String>::Element *E = q.scenes.front(); E; E = E->next()) { - Node *node = p_tile_map->get_node(E->get()); - if (node) { - node->queue_delete(); - } - } - - 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 = p_tile_map->get_cell(E_cell->get()); - - TileSetSource *source; - if (tile_set->has_source(c.source_id)) { - source = *tile_set->get_source(c.source_id); - - if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { - continue; - } - - TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source); - if (scenes_collection_source) { - Ref<PackedScene> packed_scene = scenes_collection_source->get_scene_tile_scene(c.alternative_tile); - if (packed_scene.is_valid()) { - Node *scene = packed_scene->instantiate(); - p_tile_map->add_child(scene); - 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(p_tile_map->map_to_world(E_cell->get()) + scene_as_control->get_position()); - } else if (scene_as_node2d) { - Transform2D xform; - xform.set_origin(p_tile_map->map_to_world(E_cell->get())); - scene_as_node2d->set_transform(xform * scene_as_node2d->get_transform()); - } - q.scenes[E_cell->get()] = scene->get_name(); - } - } - } - } - - q_list_element = q_list_element->next(); - } -} - -void TileSetPluginScenesCollections::cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { - // Clear the scenes. - for (Map<Vector2i, String>::Element *E = p_quadrant->scenes.front(); E; E = E->next()) { - Node *node = p_tile_map->get_node(E->get()); - if (node) { - node->queue_delete(); - } - } - - p_quadrant->scenes.clear(); -} - -void TileSetPluginScenesCollections::draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) { - Ref<TileSet> tile_set = p_tile_map->get_tileset(); - ERR_FAIL_COND(!tile_set.is_valid()); - - if (!Engine::get_singleton()->is_editor_hint()) { - return; - } - - // Draw a placeholder for scenes needing one. - RenderingServer *rs = RenderingServer::get_singleton(); - Vector2 quadrant_pos = p_tile_map->map_to_world(p_quadrant->coords * p_tile_map->get_effective_quadrant_size()); - for (Set<Vector2i>::Element *E_cell = p_quadrant->cells.front(); E_cell; E_cell = E_cell->next()) { - const TileMapCell &c = p_tile_map->get_cell(E_cell->get()); - - TileSetSource *source; - if (tile_set->has_source(c.source_id)) { - source = *tile_set->get_source(c.source_id); - - if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { - continue; - } - - TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source); - if (scenes_collection_source) { - if (!scenes_collection_source->get_scene_tile_scene(c.alternative_tile).is_valid() || scenes_collection_source->get_scene_tile_display_placeholder(c.alternative_tile)) { - // Generate a random color from the hashed values of the tiles. - Array to_hash; - to_hash.push_back(c.source_id); - to_hash.push_back(c.alternative_tile); - uint32_t hash = RandomPCG(to_hash.hash()).rand(); - - Color color; - color = color.from_hsv( - (float)((hash >> 24) & 0xFF) / 256.0, - Math::lerp(0.5, 1.0, (float)((hash >> 16) & 0xFF) / 256.0), - Math::lerp(0.5, 1.0, (float)((hash >> 8) & 0xFF) / 256.0), - 0.8); - - // Draw a placeholder tile. - Transform2D xform; - xform.set_origin(p_tile_map->map_to_world(E_cell->get()) - 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); - } - } - } - } -} diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h index dbf6dbabe6..ba7207241a 100644 --- a/scene/resources/tile_set.h +++ b/scene/resources/tile_set.h @@ -79,15 +79,15 @@ private: Vector2 tex_offset; Ref<ShaderMaterial> material; Rect2 region; - int tile_mode; - Color modulate; + int tile_mode = 0; + Color modulate = Color(1, 1, 1); // Atlas or autotiles data - int autotile_bitmask_mode; + int autotile_bitmask_mode = 0; Vector2 autotile_icon_coordinate; Size2i autotile_tile_size = Size2i(16, 16); - int autotile_spacing; + int autotile_spacing = 0; Map<Vector2i, int> autotile_bitmask_flags; Map<Vector2i, Ref<OccluderPolygon2D>> autotile_occluder_map; Map<Vector2i, Ref<NavigationPolygon>> autotile_navpoly_map; @@ -99,23 +99,29 @@ private: Vector2 occluder_offset; Ref<NavigationPolygon> navigation; Vector2 navigation_offset; - int z_index; + int z_index = 0; }; - Map<int, CompatibilityTileData *> compatibility_data = Map<int, CompatibilityTileData *>(); - Map<int, int> compatibility_source_mapping = Map<int, int>(); + enum CompatibilityTileMode { + COMPATIBILITY_TILE_MODE_SINGLE_TILE = 0, + COMPATIBILITY_TILE_MODE_AUTO_TILE, + COMPATIBILITY_TILE_MODE_ATLAS_TILE, + }; -private: - void compatibility_conversion(); + Map<int, CompatibilityTileData *> compatibility_data; + Map<int, int> compatibility_tilemap_mapping_tile_modes; + Map<int, Map<Array, Array>> compatibility_tilemap_mapping; -public: - int compatibility_get_source_for_tile_id(int p_old_source) { - return compatibility_source_mapping[p_old_source]; - }; + void _compatibility_conversion(); +public: + // Format of output array [source_id, atlas_coords, alternative] + Array compatibility_tilemap_map(int p_tile_id, Vector2i p_coords, bool p_flip_h, bool p_flip_v, bool p_transpose); #endif // DISABLE_DEPRECATED public: + static const int INVALID_SOURCE; // -1; + enum CellNeighbor { CELL_NEIGHBOR_RIGHT_SIDE = 0, CELL_NEIGHBOR_RIGHT_CORNER, @@ -165,7 +171,6 @@ public: TILE_OFFSET_AXIS_VERTICAL, }; -public: struct PackedSceneSource { Ref<PackedScene> scene; Vector2 offset; @@ -186,7 +191,6 @@ private: Vector2 tile_skew = Vector2(0, 0); // Rendering. - bool y_sorting = false; bool uv_clipping = false; struct OcclusionLayer { uint32_t light_mask = 1; @@ -221,10 +225,10 @@ private: bool terrain_bits_meshes_dirty = true; // Navigation - struct Navigationlayer { + struct NavigationLayer { uint32_t layers = 1; }; - Vector<Navigationlayer> navigation_layers; + Vector<NavigationLayer> navigation_layers; // CustomData struct CustomDataLayer { @@ -240,12 +244,14 @@ private: int next_source_id = 0; // --------------------- - // Plugins themselves. - Vector<TileSetPlugin *> tile_set_plugins_vector; - void _compute_next_source_id(); void _source_changed(); + // Tile proxies + Map<int, int> source_level_proxies; + Map<Array, Array> coords_level_proxies; + Map<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); @@ -289,22 +295,23 @@ public: Ref<TileSetSource> get_source(int p_source_id) const; // Rendering - void set_y_sorting(bool p_y_sort); - bool is_y_sorting() const; - void set_uv_clipping(bool p_uv_clipping); bool is_uv_clipping() const; - void set_occlusion_layers_count(int p_occlusion_layers_count); int get_occlusion_layers_count() const; + void add_occlusion_layer(int p_index = -1); + void move_occlusion_layer(int p_from_index, int p_to_pos); + void remove_occlusion_layer(int p_index); void set_occlusion_layer_light_mask(int p_layer_index, int p_light_mask); int get_occlusion_layer_light_mask(int p_layer_index) const; - void set_occlusion_layer_sdf_collision(int p_layer_index, int p_sdf_collision); + void set_occlusion_layer_sdf_collision(int p_layer_index, bool p_sdf_collision); bool get_occlusion_layer_sdf_collision(int p_layer_index) const; // Physics - void set_physics_layers_count(int p_physics_layers_count); int get_physics_layers_count() const; + void add_physics_layer(int p_index = -1); + void move_physics_layer(int p_from_index, int p_to_pos); + void remove_physics_layer(int p_index); void set_physics_layer_collision_layer(int p_layer_index, uint32_t p_layer); uint32_t get_physics_layer_collision_layer(int p_layer_index) const; void set_physics_layer_collision_mask(int p_layer_index, uint32_t p_mask); @@ -312,13 +319,19 @@ public: void set_physics_layer_physics_material(int p_layer_index, Ref<PhysicsMaterial> p_physics_material); Ref<PhysicsMaterial> get_physics_layer_physics_material(int p_layer_index) const; - // Terrains - void set_terrain_sets_count(int p_terrains_sets_count); + // Terrain sets int get_terrain_sets_count() const; + void add_terrain_set(int p_index = -1); + void move_terrain_set(int p_from_index, int p_to_pos); + void remove_terrain_set(int p_index); void set_terrain_set_mode(int p_terrain_set, TerrainMode p_terrain_mode); TerrainMode get_terrain_set_mode(int p_terrain_set) const; - void set_terrains_count(int p_terrain_set, int p_terrains_count); + + // Terrains int get_terrains_count(int p_terrain_set) const; + void add_terrain(int p_terrain_set, int p_index = -1); + void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos); + void remove_terrain(int p_terrain_set, int p_index); void set_terrain_name(int p_terrain_set, int p_terrain_index, String p_name); 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); @@ -327,23 +340,52 @@ public: bool is_valid_peering_bit_terrain(int p_terrain_set, TileSet::CellNeighbor p_peering_bit) const; // Navigation - void set_navigation_layers_count(int p_navigation_layers_count); int get_navigation_layers_count() const; + void add_navigation_layer(int p_index = -1); + void move_navigation_layer(int p_from_index, int p_to_pos); + void remove_navigation_layer(int p_index); void set_navigation_layer_layers(int p_layer_index, uint32_t p_layers); uint32_t get_navigation_layer_layers(int p_layer_index) const; // Custom data - void set_custom_data_layers_count(int p_custom_data_layers_count); int get_custom_data_layers_count() const; + void add_custom_data_layer(int p_index = -1); + void move_custom_data_layer(int p_from_index, int p_to_pos); + void remove_custom_data_layer(int p_index); int get_custom_data_layer_by_name(String p_value) const; void set_custom_data_name(int p_layer_id, String p_value); String get_custom_data_name(int p_layer_id) const; void set_custom_data_type(int p_layer_id, Variant::Type p_value); Variant::Type get_custom_data_type(int p_layer_id) const; + // Tiles proxies. + void set_source_level_tile_proxy(int p_source_from, int p_source_to); + int get_source_level_tile_proxy(int p_source_from); + bool has_source_level_tile_proxy(int p_source_from); + void remove_source_level_tile_proxy(int p_source_from); + + void set_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_source_to, Vector2i p_coords_to); + Array get_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from); + bool has_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from); + void remove_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from); + + void set_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from, int p_source_to, Vector2i p_coords_to, int p_alternative_to); + Array get_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from); + bool has_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from); + void remove_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from); + + Array get_source_level_tile_proxies() const; + Array get_coords_level_tile_proxies() const; + Array get_alternative_level_tile_proxies() const; + + Array map_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from) const; + + void cleanup_invalid_tile_proxies(); + void clear_tile_proxies(); + // Helpers Vector<Vector2> get_tile_shape_polygon(); - void draw_tile_shape(CanvasItem *p_canvas_item, Rect2 p_region, Color p_color, bool p_filled = false, Ref<Texture2D> p_texture = Ref<Texture2D>()); + 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); void draw_terrains(CanvasItem *p_canvas_item, Transform2D p_transform, const TileData *p_tile_data); @@ -362,6 +404,8 @@ class TileSetSource : public Resource { protected: const TileSet *tile_set = nullptr; + static void _bind_methods(); + public: static const Vector2i INVALID_ATLAS_COORDS; // Vector2i(-1, -1); static const int INVALID_TILE_ALTERNATIVE; // -1; @@ -369,6 +413,24 @@ public: // Not exposed. virtual void set_tile_set(const TileSet *p_tile_set); virtual void notify_tile_data_properties_should_change(){}; + virtual void add_occlusion_layer(int p_index){}; + virtual void move_occlusion_layer(int p_from_index, int p_to_pos){}; + virtual void remove_occlusion_layer(int p_index){}; + virtual void add_physics_layer(int p_index){}; + virtual void move_physics_layer(int p_from_index, int p_to_pos){}; + virtual void remove_physics_layer(int p_index){}; + virtual void add_terrain_set(int p_index){}; + virtual void move_terrain_set(int p_from_index, int p_to_pos){}; + virtual void remove_terrain_set(int p_index){}; + virtual void add_terrain(int p_terrain_set, int p_index){}; + virtual void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos){}; + virtual void remove_terrain(int p_terrain_set, int p_index){}; + virtual void add_navigation_layer(int p_index){}; + virtual void move_navigation_layer(int p_from_index, int p_to_pos){}; + virtual void remove_navigation_layer(int p_index){}; + virtual void add_custom_data_layer(int p_index){}; + virtual void move_custom_data_layer(int p_from_index, int p_to_pos){}; + virtual void remove_custom_data_layer(int p_index){}; virtual void reset_state() override{}; // Tiles. @@ -420,6 +482,24 @@ public: // Not exposed. virtual void set_tile_set(const TileSet *p_tile_set) override; virtual void notify_tile_data_properties_should_change() override; + virtual void add_occlusion_layer(int p_index) override; + virtual void move_occlusion_layer(int p_from_index, int p_to_pos) override; + virtual void remove_occlusion_layer(int p_index) override; + virtual void add_physics_layer(int p_index) override; + virtual void move_physics_layer(int p_from_index, int p_to_pos) override; + virtual void remove_physics_layer(int p_index) override; + virtual void add_terrain_set(int p_index) override; + virtual void move_terrain_set(int p_from_index, int p_to_pos) override; + virtual void remove_terrain_set(int p_index) override; + virtual void add_terrain(int p_terrain_set, int p_index) override; + virtual void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) override; + virtual void remove_terrain(int p_terrain_set, int p_index) override; + virtual void add_navigation_layer(int p_index) override; + virtual void move_navigation_layer(int p_from_index, int p_to_pos) override; + virtual void remove_navigation_layer(int p_index) override; + virtual void add_custom_data_layer(int p_index) override; + virtual void move_custom_data_layer(int p_from_index, int p_to_pos) override; + virtual void remove_custom_data_layer(int p_index) override; virtual void reset_state() override; // Base properties. @@ -500,7 +580,7 @@ public: int get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const override; bool has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const override; - // Scenes sccessors. Lot are similar to "Alternative tiles". + // Scenes accessors. Lot are similar to "Alternative tiles". int get_scene_tiles_count() { return get_alternative_tiles_count(Vector2i()); } int get_scene_tile_id(int p_index) { return get_alternative_tile_id(Vector2i(), p_index); }; bool has_scene_tile_id(int p_id) { return has_alternative_tile(Vector2i(), p_id); }; @@ -569,6 +649,24 @@ public: // Not exposed. void set_tile_set(const TileSet *p_tile_set); void notify_tile_data_properties_should_change(); + void add_occlusion_layer(int p_index); + void move_occlusion_layer(int p_from_index, int p_to_pos); + void remove_occlusion_layer(int p_index); + void add_physics_layer(int p_index); + void move_physics_layer(int p_from_index, int p_to_pos); + void remove_physics_layer(int p_index); + void add_terrain_set(int p_index); + void move_terrain_set(int p_from_index, int p_to_pos); + void remove_terrain_set(int p_index); + void add_terrain(int p_terrain_set, int p_index); + void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos); + void remove_terrain(int p_terrain_set, int p_index); + void add_navigation_layer(int p_index); + void move_navigation_layer(int p_from_index, int p_to_pos); + void remove_navigation_layer(int p_index); + void add_custom_data_layer(int p_index); + void move_custom_data_layer(int p_from_index, int p_to_pos); + void remove_custom_data_layer(int p_index); void reset_state(); void set_allow_transform(bool p_allow_transform); bool is_allowing_transform() const; @@ -583,8 +681,8 @@ public: void set_texture_offset(Vector2i p_texture_offset); Vector2i get_texture_offset() const; - void tile_set_material(Ref<ShaderMaterial> p_material); - Ref<ShaderMaterial> tile_get_material() const; + void set_material(Ref<ShaderMaterial> p_material); + Ref<ShaderMaterial> get_material() const; void set_modulate(Color p_modulate); Color get_modulate() const; void set_z_index(int p_z_index); @@ -631,73 +729,6 @@ public: Variant get_custom_data_by_layer_id(int p_layer_id) const; }; -#include "scene/2d/tile_map.h" - -class TileSetPlugin : public Object { - GDCLASS(TileSetPlugin, Object); - -public: - // Tilemap updates. - virtual void tilemap_notification(TileMap *p_tile_map, int p_what){}; - virtual void update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list){}; - virtual void create_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant){}; - virtual void cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant){}; - - virtual void draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant){}; -}; - -class TileSetPluginAtlasRendering : public TileSetPlugin { - GDCLASS(TileSetPluginAtlasRendering, TileSetPlugin); - -private: - static constexpr float fp_adjust = 0.00001; - bool quadrant_order_dirty = false; - -public: - // Tilemap updates - virtual void tilemap_notification(TileMap *p_tile_map, int p_what) override; - virtual void update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) override; - virtual void create_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; - virtual void cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; - virtual void draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; - - // Other. - static void draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, Color p_modulation = Color(1.0, 1.0, 1.0, 1.0)); -}; - -class TileSetPluginAtlasPhysics : public TileSetPlugin { - GDCLASS(TileSetPluginAtlasPhysics, TileSetPlugin); - -public: - // Tilemap updates - virtual void tilemap_notification(TileMap *p_tile_map, int p_what) override; - virtual void update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) override; - virtual void create_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; - virtual void cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; - virtual void draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; -}; - -class TileSetPluginAtlasNavigation : public TileSetPlugin { - GDCLASS(TileSetPluginAtlasNavigation, TileSetPlugin); - -public: - // Tilemap updates - virtual void tilemap_notification(TileMap *p_tile_map, int p_what) override; - virtual void update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) override; - virtual void cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; - virtual void draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; -}; - -class TileSetPluginScenesCollections : public TileSetPlugin { - GDCLASS(TileSetPluginScenesCollections, TileSetPlugin); - -public: - // Tilemap updates - virtual void update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) override; - virtual void cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; - virtual void draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override; -}; - VARIANT_ENUM_CAST(TileSet::CellNeighbor); VARIANT_ENUM_CAST(TileSet::TerrainMode); VARIANT_ENUM_CAST(TileSet::TileShape); diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 54bc7382db..e8fe3ff3cd 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -261,54 +261,47 @@ VisualShaderNode::VisualShaderNode() { ///////////////////////////////////////////////////////// void VisualShaderNodeCustom::update_ports() { - ERR_FAIL_COND(!get_script_instance()); + { + input_ports.clear(); + int input_port_count; + if (GDVIRTUAL_CALL(_get_input_port_count, input_port_count)) { + for (int i = 0; i < input_port_count; i++) { + Port port; + if (!GDVIRTUAL_CALL(_get_input_port_name, i, port.name)) { + port.name = "in" + itos(i); + } + if (!GDVIRTUAL_CALL(_get_input_port_type, i, port.type)) { + port.type = (int)PortType::PORT_TYPE_SCALAR; + } - input_ports.clear(); - if (get_script_instance()->has_method("_get_input_port_count")) { - int input_port_count = (int)get_script_instance()->call("_get_input_port_count"); - bool has_name = get_script_instance()->has_method("_get_input_port_name"); - bool has_type = get_script_instance()->has_method("_get_input_port_type"); - for (int i = 0; i < input_port_count; i++) { - Port port; - if (has_name) { - port.name = (String)get_script_instance()->call("_get_input_port_name", i); - } else { - port.name = "in" + itos(i); - } - if (has_type) { - port.type = (int)get_script_instance()->call("_get_input_port_type", i); - } else { - port.type = (int)PortType::PORT_TYPE_SCALAR; + input_ports.push_back(port); } - input_ports.push_back(port); } } - output_ports.clear(); - if (get_script_instance()->has_method("_get_output_port_count")) { - int output_port_count = (int)get_script_instance()->call("_get_output_port_count"); - bool has_name = get_script_instance()->has_method("_get_output_port_name"); - bool has_type = get_script_instance()->has_method("_get_output_port_type"); - for (int i = 0; i < output_port_count; i++) { - Port port; - if (has_name) { - port.name = (String)get_script_instance()->call("_get_output_port_name", i); - } else { - port.name = "out" + itos(i); - } - if (has_type) { - port.type = (int)get_script_instance()->call("_get_output_port_type", i); - } else { - port.type = (int)PortType::PORT_TYPE_SCALAR; + + { + output_ports.clear(); + int output_port_count; + if (GDVIRTUAL_CALL(_get_output_port_count, output_port_count)) { + for (int i = 0; i < output_port_count; i++) { + Port port; + if (!GDVIRTUAL_CALL(_get_output_port_name, i, port.name)) { + port.name = "out" + itos(i); + } + if (!GDVIRTUAL_CALL(_get_output_port_type, i, port.type)) { + port.type = (int)PortType::PORT_TYPE_SCALAR; + } + + output_ports.push_back(port); } - output_ports.push_back(port); } } } String VisualShaderNodeCustom::get_caption() const { - ERR_FAIL_COND_V(!get_script_instance(), ""); - if (get_script_instance()->has_method("_get_name")) { - return (String)get_script_instance()->call("_get_name"); + String ret; + if (GDVIRTUAL_CALL(_get_name, ret)) { + return ret; } return "Unnamed"; } @@ -342,9 +335,8 @@ String VisualShaderNodeCustom::get_output_port_name(int p_port) const { } String VisualShaderNodeCustom::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 { - ERR_FAIL_COND_V(!get_script_instance(), ""); - ERR_FAIL_COND_V(!get_script_instance()->has_method("_get_code"), ""); - Array input_vars; + ERR_FAIL_COND_V(!GDVIRTUAL_IS_OVERRIDDEN(_get_code), ""); + Vector<String> input_vars; for (int i = 0; i < get_input_port_count(); i++) { input_vars.push_back(p_input_vars[i]); } @@ -352,14 +344,15 @@ String VisualShaderNodeCustom::generate_code(Shader::Mode p_mode, VisualShader:: for (int i = 0; i < get_output_port_count(); i++) { output_vars.push_back(p_output_vars[i]); } - String code = "\t{\n"; - String _code = (String)get_script_instance()->call("_get_code", input_vars, output_vars, (int)p_mode, (int)p_type); + String code = " {\n"; + String _code; + GDVIRTUAL_CALL(_get_code, input_vars, output_vars, (int)p_mode, (int)p_type, _code); bool nend = _code.ends_with("\n"); - _code = _code.insert(0, "\t\t"); - _code = _code.replace("\n", "\n\t\t"); + _code = _code.insert(0, " "); + _code = _code.replace("\n", "\n "); code += _code; if (!nend) { - code += "\n\t}"; + code += "\n }"; } else { code.remove(code.size() - 1); code += "}"; @@ -369,10 +362,10 @@ String VisualShaderNodeCustom::generate_code(Shader::Mode p_mode, VisualShader:: } String VisualShaderNodeCustom::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { - ERR_FAIL_COND_V(!get_script_instance(), ""); - if (get_script_instance()->has_method("_get_global_code")) { + String ret; + if (GDVIRTUAL_CALL(_get_global_code, (int)p_mode, ret)) { String code = "// " + get_caption() + "\n"; - code += (String)get_script_instance()->call("_get_global_code", (int)p_mode); + code += ret; code += "\n"; return code; } @@ -416,19 +409,19 @@ void VisualShaderNodeCustom::_set_initialized(bool p_enabled) { } void VisualShaderNodeCustom::_bind_methods() { - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_name")); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_description")); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_category")); - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_return_icon_type")); - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_input_port_count")); - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_input_port_type", PropertyInfo(Variant::INT, "port"))); - BIND_VMETHOD(MethodInfo(Variant::STRING_NAME, "_get_input_port_name", PropertyInfo(Variant::INT, "port"))); - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_output_port_count")); - BIND_VMETHOD(MethodInfo(Variant::INT, "_get_output_port_type", PropertyInfo(Variant::INT, "port"))); - BIND_VMETHOD(MethodInfo(Variant::STRING_NAME, "_get_output_port_name", PropertyInfo(Variant::INT, "port"))); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_code", PropertyInfo(Variant::ARRAY, "input_vars"), PropertyInfo(Variant::ARRAY, "output_vars"), PropertyInfo(Variant::INT, "mode"), PropertyInfo(Variant::INT, "type"))); - BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_global_code", PropertyInfo(Variant::INT, "mode"))); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_highend")); + GDVIRTUAL_BIND(_get_name); + GDVIRTUAL_BIND(_get_description); + GDVIRTUAL_BIND(_get_category); + GDVIRTUAL_BIND(_get_return_icon_type); + GDVIRTUAL_BIND(_get_input_port_count); + GDVIRTUAL_BIND(_get_input_port_type, "port"); + GDVIRTUAL_BIND(_get_input_port_name, "port"); + GDVIRTUAL_BIND(_get_output_port_count); + GDVIRTUAL_BIND(_get_output_port_type, "port"); + GDVIRTUAL_BIND(_get_output_port_name, "port"); + GDVIRTUAL_BIND(_get_code, "input_vars", "output_vars", "mode", "type"); + GDVIRTUAL_BIND(_get_global_code, "mode"); + GDVIRTUAL_BIND(_is_highend); ClassDB::bind_method(D_METHOD("_set_initialized", "enabled"), &VisualShaderNodeCustom::_set_initialized); ClassDB::bind_method(D_METHOD("_is_initialized"), &VisualShaderNodeCustom::_is_initialized); @@ -451,18 +444,29 @@ VisualShader::Type VisualShader::get_shader_type() const { return current_type; } -void VisualShader::set_version(const String &p_version) { - version = p_version; +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"]; } -String VisualShader::get_version() const { - return version; +Dictionary VisualShader::get_engine_version() const { + return engine_version; } -void VisualShader::update_version(const String &p_new_version) { - if (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 (Map<int, Node>::Element *E = graph[i].nodes.front(); E; E = E->next()) { + Ref<VisualShaderNodeInput> input = Object::cast_to<VisualShaderNodeInput>(E->get().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->get().node.ptr()); if (expression.is_valid()) { for (int j = 0; j < expression->get_input_port_count(); j++) { @@ -491,9 +495,11 @@ void VisualShader::update_version(const String &p_new_version) { } } } - set_version(p_new_version); + 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); @@ -628,8 +634,8 @@ bool VisualShader::is_node_connection(Type p_type, int p_from_node, int p_from_p ERR_FAIL_INDEX_V(p_type, TYPE_MAX, false); const Graph *g = &graph[p_type]; - for (const List<Connection>::Element *E = g->connections.front(); E; E = E->next()) { - if (E->get().from_node == p_from_node && E->get().from_port == p_from_port && E->get().to_node == p_to_node && E->get().to_port == p_to_port) { + for (const Connection &E : g->connections) { + if (E.from_node == p_from_node && E.from_port == p_from_port && E.to_node == p_to_node && E.to_port == p_to_port) { return true; } } @@ -642,12 +648,12 @@ bool VisualShader::is_nodes_connected_relatively(const Graph *p_graph, int p_nod const VisualShader::Node &node = p_graph->nodes[p_node]; - for (const List<int>::Element *E = node.prev_connected_nodes.front(); E; E = E->next()) { - if (E->get() == p_target) { + for (const int &E : node.prev_connected_nodes) { + if (E == p_target) { return true; } - result = is_nodes_connected_relatively(p_graph, E->get(), p_target); + result = is_nodes_connected_relatively(p_graph, E, p_target); if (result) { break; } @@ -686,8 +692,8 @@ bool VisualShader::can_connect_nodes(Type p_type, int p_from_node, int p_from_po return false; } - for (const List<Connection>::Element *E = g->connections.front(); E; E = E->next()) { - if (E->get().from_node == p_from_node && E->get().from_port == p_from_port && E->get().to_node == p_to_node && E->get().to_port == p_to_port) { + for (const Connection &E : g->connections) { + if (E.from_node == p_from_node && E.from_port == p_from_port && E.to_node == p_to_node && E.to_port == p_to_port) { return false; } } @@ -739,8 +745,8 @@ Error VisualShader::connect_nodes(Type p_type, int p_from_node, int p_from_port, ERR_FAIL_COND_V_MSG(!is_port_types_compatible(from_port_type, to_port_type), ERR_INVALID_PARAMETER, "Incompatible port types (scalar/vec/bool) with transform."); - for (List<Connection>::Element *E = g->connections.front(); E; E = E->next()) { - if (E->get().from_node == p_from_node && E->get().from_port == p_from_port && E->get().to_node == p_to_node && E->get().to_port == p_to_port) { + for (const Connection &E : g->connections) { + if (E.from_node == p_from_node && E.from_port == p_from_port && E.to_node == p_to_node && E.to_port == p_to_port) { ERR_FAIL_V(ERR_ALREADY_EXISTS); } } @@ -763,7 +769,7 @@ void VisualShader::disconnect_nodes(Type p_type, int p_from_node, int p_from_por ERR_FAIL_INDEX(p_type, TYPE_MAX); Graph *g = &graph[p_type]; - for (List<Connection>::Element *E = g->connections.front(); E; E = E->next()) { + for (const List<Connection>::Element *E = g->connections.front(); E; E = E->next()) { if (E->get().from_node == p_from_node && E->get().from_port == p_from_port && E->get().to_node == p_to_node && E->get().to_port == p_to_port) { g->connections.erase(E); g->nodes[p_to_node].prev_connected_nodes.erase(p_from_node); @@ -780,12 +786,12 @@ Array VisualShader::_get_node_connections(Type p_type) const { const Graph *g = &graph[p_type]; Array ret; - for (const List<Connection>::Element *E = g->connections.front(); E; E = E->next()) { + for (const Connection &E : g->connections) { Dictionary d; - d["from_node"] = E->get().from_node; - d["from_port"] = E->get().from_port; - d["to_node"] = E->get().to_node; - d["to_port"] = E->get().to_port; + d["from_node"] = E.from_node; + d["from_port"] = E.from_port; + d["to_node"] = E.to_node; + d["to_port"] = E.to_port; ret.push_back(d); } @@ -796,8 +802,8 @@ void VisualShader::get_node_connections(Type p_type, List<Connection> *r_connect ERR_FAIL_INDEX(p_type, TYPE_MAX); const Graph *g = &graph[p_type]; - for (const List<Connection>::Element *E = g->connections.front(); E; E = E->next()) { - r_connections->push_back(E->get()); + for (const Connection &E : g->connections) { + r_connections->push_back(E); } } @@ -900,7 +906,7 @@ String VisualShader::generate_preview_shader(Type p_type, int p_node, int p_port String expr = ""; expr += "// " + global_expression->get_caption() + ":" + itos(index++) + "\n"; expr += global_expression->generate_global(get_mode(), Type(i), -1); - expr = expr.replace("\n", "\n\t"); + expr = expr.replace("\n", "\n "); expr += "\n"; global_expressions += expr; } @@ -935,13 +941,13 @@ String VisualShader::generate_preview_shader(Type p_type, int p_node, int p_port ERR_FAIL_COND_V(err != OK, String()); if (node->get_output_port_type(p_port) == VisualShaderNode::PORT_TYPE_SCALAR) { - code += "\tCOLOR.rgb = vec3(n_out" + itos(p_node) + "p" + itos(p_port) + " );\n"; + code += " COLOR.rgb = vec3(n_out" + itos(p_node) + "p" + itos(p_port) + " );\n"; } else if (node->get_output_port_type(p_port) == VisualShaderNode::PORT_TYPE_SCALAR_INT) { - code += "\tCOLOR.rgb = vec3(float(n_out" + itos(p_node) + "p" + itos(p_port) + "));\n"; + code += " COLOR.rgb = vec3(float(n_out" + itos(p_node) + "p" + itos(p_port) + "));\n"; } else if (node->get_output_port_type(p_port) == VisualShaderNode::PORT_TYPE_BOOLEAN) { - code += "\tCOLOR.rgb = vec3(n_out" + itos(p_node) + "p" + itos(p_port) + " ? 1.0 : 0.0);\n"; + code += " COLOR.rgb = vec3(n_out" + itos(p_node) + "p" + itos(p_port) + " ? 1.0 : 0.0);\n"; } else { - code += "\tCOLOR.rgb = n_out" + itos(p_node) + "p" + itos(p_port) + ";\n"; + code += " COLOR.rgb = n_out" + itos(p_node) + "p" + itos(p_port) + ";\n"; } code += "}\n"; @@ -1190,11 +1196,11 @@ bool VisualShader::_get(const StringName &p_name, Variant &r_ret) const { String index = name.get_slicec('/', 2); if (index == "connections") { Vector<int> conns; - for (const List<Connection>::Element *E = graph[type].connections.front(); E; E = E->next()) { - conns.push_back(E->get().from_node); - conns.push_back(E->get().from_port); - conns.push_back(E->get().to_node); - conns.push_back(E->get().to_port); + for (const Connection &E : graph[type].connections) { + conns.push_back(E.from_node); + conns.push_back(E.from_port); + conns.push_back(E.to_node); + conns.push_back(E.to_port); } r_ret = conns; @@ -1302,7 +1308,7 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui if (vsnode->is_disabled()) { code += "// " + vsnode->get_caption() + ":" + itos(node) + "\n"; - code += "\t// Node is disabled and code is not generated.\n"; + code += " // Node is disabled and code is not generated.\n"; return OK; } @@ -1432,19 +1438,19 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui if (defval.get_type() == Variant::FLOAT) { float val = defval; inputs[i] = "n_in" + itos(node) + "p" + itos(i); - node_code += "\tfloat " + inputs[i] + " = " + vformat("%.5f", val) + ";\n"; + node_code += " float " + inputs[i] + " = " + vformat("%.5f", val) + ";\n"; } else if (defval.get_type() == Variant::INT) { int val = defval; inputs[i] = "n_in" + itos(node) + "p" + itos(i); - node_code += "\tint " + inputs[i] + " = " + itos(val) + ";\n"; + node_code += " int " + inputs[i] + " = " + itos(val) + ";\n"; } else if (defval.get_type() == Variant::BOOL) { bool val = defval; inputs[i] = "n_in" + itos(node) + "p" + itos(i); - node_code += "\tbool " + inputs[i] + " = " + (val ? "true" : "false") + ";\n"; + node_code += " bool " + inputs[i] + " = " + (val ? "true" : "false") + ";\n"; } else if (defval.get_type() == Variant::VECTOR3) { Vector3 val = defval; inputs[i] = "n_in" + itos(node) + "p" + itos(i); - node_code += "\tvec3 " + inputs[i] + " = " + vformat("vec3(%.5f, %.5f, %.5f);\n", val.x, val.y, val.z); + node_code += " vec3 " + inputs[i] + " = " + vformat("vec3(%.5f, %.5f, %.5f);\n", val.x, val.y, val.z); } else if (defval.get_type() == Variant::TRANSFORM3D) { Transform3D val = defval; val.basis.transpose(); @@ -1459,7 +1465,7 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui values.push_back(val.origin.y); values.push_back(val.origin.z); bool err = false; - node_code += "\tmat4 " + inputs[i] + " = " + String("mat4(vec4(%.5f, %.5f, %.5f, 0.0), vec4(%.5f, %.5f, %.5f, 0.0), vec4(%.5f, %.5f, %.5f, 0.0), vec4(%.5f, %.5f, %.5f, 1.0));\n").sprintf(values, &err); + node_code += " mat4 " + inputs[i] + " = " + String("mat4(vec4(%.5f, %.5f, %.5f, 0.0), vec4(%.5f, %.5f, %.5f, 0.0), vec4(%.5f, %.5f, %.5f, 0.0), vec4(%.5f, %.5f, %.5f, 1.0));\n").sprintf(values, &err); } else { //will go empty, node is expected to know what it is doing at this point and handle it } @@ -1522,19 +1528,19 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui outputs[i] = "n_out" + itos(node) + "p" + itos(j); switch (vsnode->get_output_port_type(i)) { case VisualShaderNode::PORT_TYPE_SCALAR: - code += "\tfloat " + outputs[i] + ";\n"; + code += " float " + outputs[i] + ";\n"; break; case VisualShaderNode::PORT_TYPE_SCALAR_INT: - code += "\tint " + outputs[i] + ";\n"; + code += " int " + outputs[i] + ";\n"; break; case VisualShaderNode::PORT_TYPE_VECTOR: - code += "\tvec3 " + outputs[i] + ";\n"; + code += " vec3 " + outputs[i] + ";\n"; break; case VisualShaderNode::PORT_TYPE_BOOLEAN: - code += "\tbool " + outputs[i] + ";\n"; + code += " bool " + outputs[i] + ";\n"; break; case VisualShaderNode::PORT_TYPE_TRANSFORM: - code += "\tmat4 " + outputs[i] + ";\n"; + code += " mat4 " + outputs[i] + ";\n"; break; default: { } @@ -1564,7 +1570,7 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui new_line_inserted = true; } String r = "n_out" + itos(node) + "p" + itos(i + 1); - code += "\tfloat " + r + " = n_out" + itos(node) + "p" + itos(i) + ".r;\n"; + code += " float " + r + " = n_out" + itos(node) + "p" + itos(i) + ".r;\n"; outputs[i + 1] = r; } @@ -1574,7 +1580,7 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui new_line_inserted = true; } String g = "n_out" + itos(node) + "p" + itos(i + 2); - code += "\tfloat " + g + " = n_out" + itos(node) + "p" + itos(i) + ".g;\n"; + code += " float " + g + " = n_out" + itos(node) + "p" + itos(i) + ".g;\n"; outputs[i + 2] = g; } @@ -1584,7 +1590,7 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui new_line_inserted = true; } String b = "n_out" + itos(node) + "p" + itos(i + 3); - code += "\tfloat " + b + " = n_out" + itos(node) + "p" + itos(i) + ".b;\n"; + code += " float " + b + " = n_out" + itos(node) + "p" + itos(i) + ".b;\n"; outputs[i + 3] = b; } @@ -1635,19 +1641,10 @@ void VisualShader::_update_shader() const { { //fill render mode enums int idx = 0; - bool specular = false; while (render_mode_enums[idx].string) { if (shader_mode == render_mode_enums[idx].mode) { - if (shader_mode == Shader::MODE_SPATIAL) { - if (String(render_mode_enums[idx].string) == "specular") { - specular = true; - } - } - if (modes.has(render_mode_enums[idx].string) || specular) { - int which = 0; - if (modes.has(render_mode_enums[idx].string)) { - which = modes[render_mode_enums[idx].string]; - } + if (modes.has(render_mode_enums[idx].string)) { + int which = modes[render_mode_enums[idx].string]; int count = 0; for (int i = 0; i < ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader_mode)).size(); i++) { String mode = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader_mode))[i]; @@ -1701,7 +1698,7 @@ void VisualShader::_update_shader() const { String expr = ""; expr += "// " + global_expression->get_caption() + ":" + itos(index++) + "\n"; expr += global_expression->generate_global(get_mode(), Type(i), -1); - expr = expr.replace("\n", "\n\t"); + expr = expr.replace("\n", "\n "); expr += "\n"; global_expressions += expr; } @@ -1740,6 +1737,7 @@ void VisualShader::_update_shader() const { } Map<int, String> code_map; + Set<int> empty_funcs; for (int i = 0; i < TYPE_MAX; i++) { if (!has_func_name(RenderingServer::ShaderMode(shader_mode), func_name[i])) { @@ -1752,6 +1750,11 @@ void VisualShader::_update_shader() const { StringBuilder func_code; + bool is_empty_func = false; + if (shader_mode != Shader::MODE_PARTICLES && shader_mode != Shader::MODE_SKY) { + is_empty_func = true; + } + for (const List<Connection>::Element *E = graph[i].connections.front(); E; E = E->next()) { ConnectionKey from_key; from_key.node = E->get().from_node; @@ -1764,7 +1767,17 @@ void VisualShader::_update_shader() const { to_key.port = E->get().to_port; input_connections.insert(to_key, E); + + if (is_empty_func && to_key.node == NODE_ID_OUTPUT) { + is_empty_func = false; + } + } + + if (is_empty_func) { + empty_funcs.insert(i); + continue; } + if (shader_mode != Shader::MODE_PARTICLES) { func_code += "\nvoid " + String(func_name[i]) + "() {\n"; } @@ -1775,8 +1788,8 @@ void VisualShader::_update_shader() const { ERR_FAIL_COND(err != OK); if (emitters.has(i)) { - for (List<int>::Element *E = emitters[i].front(); E; E = E->next()) { - err = _write_node(Type(i), global_code, global_code_per_node, global_code_per_func, func_code, default_tex_params, input_connections, output_connections, E->get(), processed, false, classes); + for (int &E : emitters[i]) { + err = _write_node(Type(i), global_code, global_code_per_node, global_code_per_func, func_code, default_tex_params, input_connections, output_connections, E, processed, false, classes); ERR_FAIL_COND(err != OK); } } @@ -1800,112 +1813,112 @@ void VisualShader::_update_shader() const { code += "void start() {\n"; if (has_start || has_start_custom) { - code += "\tuint __seed = __hash(NUMBER + uint(1) + RANDOM_SEED);\n"; - code += "\tvec3 __diff = TRANSFORM[3].xyz - EMISSION_TRANSFORM[3].xyz;\n"; - code += "\tfloat __radians;\n"; - code += "\tvec3 __vec3_buff1;\n"; - code += "\tvec3 __vec3_buff2;\n"; - code += "\tfloat __scalar_buff1;\n"; - code += "\tfloat __scalar_buff2;\n"; - code += "\tvec3 __ndiff = normalize(__diff);\n\n"; + code += " uint __seed = __hash(NUMBER + uint(1) + RANDOM_SEED);\n"; + code += " vec3 __diff = TRANSFORM[3].xyz - EMISSION_TRANSFORM[3].xyz;\n"; + code += " float __radians;\n"; + code += " vec3 __vec3_buff1;\n"; + code += " vec3 __vec3_buff2;\n"; + code += " float __scalar_buff1;\n"; + code += " float __scalar_buff2;\n"; + code += " vec3 __ndiff = normalize(__diff);\n\n"; } if (has_start) { - code += "\t{\n"; - code += code_map[TYPE_START].replace("\n\t", "\n\t\t"); - code += "\t}\n"; + code += " {\n"; + code += code_map[TYPE_START].replace("\n ", "\n "); + code += " }\n"; if (has_start_custom) { - code += "\t\n"; + code += " \n"; } } if (has_start_custom) { - code += "\t{\n"; - code += code_map[TYPE_START_CUSTOM].replace("\n\t", "\n\t\t"); - code += "\t}\n"; + code += " {\n"; + code += code_map[TYPE_START_CUSTOM].replace("\n ", "\n "); + code += " }\n"; } code += "}\n\n"; code += "void process() {\n"; if (has_process || has_process_custom || has_collide) { - code += "\tuint __seed = __hash(NUMBER + uint(1) + RANDOM_SEED);\n"; - code += "\tvec3 __vec3_buff1;\n"; - code += "\tvec3 __diff = TRANSFORM[3].xyz - EMISSION_TRANSFORM[3].xyz;\n"; - code += "\tvec3 __ndiff = normalize(__diff);\n\n"; + code += " uint __seed = __hash(NUMBER + uint(1) + RANDOM_SEED);\n"; + code += " vec3 __vec3_buff1;\n"; + code += " vec3 __diff = TRANSFORM[3].xyz - EMISSION_TRANSFORM[3].xyz;\n"; + code += " vec3 __ndiff = normalize(__diff);\n\n"; } - code += "\t{\n"; - String tab = "\t"; + code += " {\n"; + String tab = " "; if (has_collide) { - code += "\t\tif (COLLIDED) {\n\n"; - code += code_map[TYPE_COLLIDE].replace("\n\t", "\n\t\t\t"); + code += " if (COLLIDED) {\n\n"; + code += code_map[TYPE_COLLIDE].replace("\n ", "\n "); if (has_process) { - code += "\t\t} else {\n\n"; - tab += "\t"; + code += " } else {\n\n"; + tab += " "; } } if (has_process) { - code += code_map[TYPE_PROCESS].replace("\n\t", "\n\t" + tab); + code += code_map[TYPE_PROCESS].replace("\n ", "\n " + tab); } if (has_collide) { - code += "\t\t}\n"; + code += " }\n"; } - code += "\t}\n"; + code += " }\n"; if (has_process_custom) { - code += "\t{\n\n"; - code += code_map[TYPE_PROCESS_CUSTOM].replace("\n\t", "\n\t\t"); - code += "\t}\n"; + code += " {\n\n"; + code += code_map[TYPE_PROCESS_CUSTOM].replace("\n ", "\n "); + code += " }\n"; } code += "}\n\n"; global_compute_code += "float __rand_from_seed(inout uint seed) {\n"; - global_compute_code += "\tint k;\n"; - global_compute_code += "\tint s = int(seed);\n"; - global_compute_code += "\tif (s == 0)\n"; - global_compute_code += "\ts = 305420679;\n"; - global_compute_code += "\tk = s / 127773;\n"; - global_compute_code += "\ts = 16807 * (s - k * 127773) - 2836 * k;\n"; - global_compute_code += "\tif (s < 0)\n"; - global_compute_code += "\t\ts += 2147483647;\n"; - global_compute_code += "\tseed = uint(s);\n"; - global_compute_code += "\treturn float(seed % uint(65536)) / 65535.0;\n"; + global_compute_code += " int k;\n"; + global_compute_code += " int s = int(seed);\n"; + global_compute_code += " if (s == 0)\n"; + global_compute_code += " s = 305420679;\n"; + global_compute_code += " k = s / 127773;\n"; + global_compute_code += " s = 16807 * (s - k * 127773) - 2836 * k;\n"; + global_compute_code += " if (s < 0)\n"; + global_compute_code += " s += 2147483647;\n"; + global_compute_code += " seed = uint(s);\n"; + global_compute_code += " return float(seed % uint(65536)) / 65535.0;\n"; global_compute_code += "}\n\n"; global_compute_code += "float __rand_from_seed_m1_p1(inout uint seed) {\n"; - global_compute_code += "\treturn __rand_from_seed(seed) * 2.0 - 1.0;\n"; + global_compute_code += " return __rand_from_seed(seed) * 2.0 - 1.0;\n"; global_compute_code += "}\n\n"; global_compute_code += "float __randf_range(inout uint seed, float from, float to) {\n"; - global_compute_code += "\treturn __rand_from_seed(seed) * (to - from) + from;\n"; + global_compute_code += " return __rand_from_seed(seed) * (to - from) + from;\n"; global_compute_code += "}\n\n"; global_compute_code += "vec3 __randv_range(inout uint seed, vec3 from, vec3 to) {\n"; - global_compute_code += "\treturn vec3(__randf_range(seed, from.x, to.x), __randf_range(seed, from.y, to.y), __randf_range(seed, from.z, to.z));\n"; + global_compute_code += " return vec3(__randf_range(seed, from.x, to.x), __randf_range(seed, from.y, to.y), __randf_range(seed, from.z, to.z));\n"; global_compute_code += "}\n\n"; global_compute_code += "uint __hash(uint x) {\n"; - global_compute_code += "\tx = ((x >> uint(16)) ^ x) * uint(73244475);\n"; - global_compute_code += "\tx = ((x >> uint(16)) ^ x) * uint(73244475);\n"; - global_compute_code += "\tx = (x >> uint(16)) ^ x;\n"; - global_compute_code += "\treturn x;\n"; + global_compute_code += " x = ((x >> uint(16)) ^ x) * uint(73244475);\n"; + global_compute_code += " x = ((x >> uint(16)) ^ x) * uint(73244475);\n"; + global_compute_code += " x = (x >> uint(16)) ^ x;\n"; + global_compute_code += " return x;\n"; global_compute_code += "}\n\n"; global_compute_code += "mat3 __build_rotation_mat3(vec3 axis, float angle) {\n"; - global_compute_code += "\taxis = normalize(axis);\n"; - global_compute_code += "\tfloat s = sin(angle);\n"; - global_compute_code += "\tfloat c = cos(angle);\n"; - global_compute_code += "\tfloat oc = 1.0 - c;\n"; - global_compute_code += "\treturn mat3(vec3(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s), vec3(oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s), vec3(oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c));\n"; + global_compute_code += " axis = normalize(axis);\n"; + global_compute_code += " float s = sin(angle);\n"; + global_compute_code += " float c = cos(angle);\n"; + global_compute_code += " float oc = 1.0 - c;\n"; + global_compute_code += " return mat3(vec3(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s), vec3(oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s), vec3(oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c));\n"; global_compute_code += "}\n\n"; global_compute_code += "mat4 __build_rotation_mat4(vec3 axis, float angle) {\n"; - global_compute_code += "\taxis = normalize(axis);\n"; - global_compute_code += "\tfloat s = sin(angle);\n"; - global_compute_code += "\tfloat c = cos(angle);\n"; - global_compute_code += "\tfloat oc = 1.0 - c;\n"; - global_compute_code += "\treturn mat4(vec4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0), vec4(oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0), vec4(oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0), vec4(0, 0, 0, 1));\n"; + global_compute_code += " axis = normalize(axis);\n"; + global_compute_code += " float s = sin(angle);\n"; + global_compute_code += " float c = cos(angle);\n"; + global_compute_code += " float oc = 1.0 - c;\n"; + global_compute_code += " return mat4(vec4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0), vec4(oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0), vec4(oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0), vec4(0, 0, 0, 1));\n"; global_compute_code += "}\n\n"; global_compute_code += "vec3 __get_random_unit_vec3(inout uint seed) {\n"; - global_compute_code += "\treturn normalize(vec3(__rand_from_seed_m1_p1(seed), __rand_from_seed_m1_p1(seed), __rand_from_seed_m1_p1(seed)));\n"; + global_compute_code += " return normalize(vec3(__rand_from_seed_m1_p1(seed), __rand_from_seed_m1_p1(seed), __rand_from_seed_m1_p1(seed)));\n"; global_compute_code += "}\n\n"; } @@ -1920,7 +1933,11 @@ void VisualShader::_update_shader() const { if (!has_func_name(RenderingServer::ShaderMode(shader_mode), func_name[i])) { continue; } - tcode = tcode.insert(insertion_pos[i], global_code_per_func[Type(i)]); + String func_code = global_code_per_func[Type(i)].as_string(); + if (empty_funcs.has(Type(i)) && !func_code.is_empty()) { + func_code = vformat("%s%s%s", String("\nvoid " + String(func_name[i]) + "() {\n"), func_code, "}\n"); + } + tcode = tcode.insert(insertion_pos[i], func_code); } final_code += tcode; @@ -1929,7 +1946,7 @@ void VisualShader::_update_shader() const { const_cast<VisualShader *>(this)->set_default_texture_param(default_tex_params[i].name, default_tex_params[i].param); } if (previous_code != final_code) { - const_cast<VisualShader *>(this)->emit_signal("changed"); + const_cast<VisualShader *>(this)->emit_signal(SNAME("changed")); } previous_code = final_code; } @@ -1940,7 +1957,7 @@ void VisualShader::_queue_update() { } dirty.set(); - call_deferred("_update_shader"); + call_deferred(SNAME("_update_shader")); } void VisualShader::_input_type_changed(Type p_type, int p_id) { @@ -1987,8 +2004,8 @@ void VisualShader::_bind_methods() { ClassDB::bind_method(D_METHOD("get_node_connections", "type"), &VisualShader::_get_node_connections); - ClassDB::bind_method(D_METHOD("set_version", "version"), &VisualShader::set_version); - ClassDB::bind_method(D_METHOD("get_version"), &VisualShader::get_version); + 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); @@ -1996,7 +2013,7 @@ 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_NOEDITOR), "set_graph_offset", "get_graph_offset"); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "version", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_version", "get_version"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "engine_version", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_engine_version", "get_engine_version"); ADD_PROPERTY_DEFAULT("code", ""); // Inherited from Shader, prevents showing default code as override in docs. @@ -2039,7 +2056,9 @@ VisualShader::VisualShader() { /////////////////////////////////////////////////////////// const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { - // Spatial, Vertex + // Node3D + + // Node3D, Vertex { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "vertex", "VERTEX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "NORMAL" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "tangent", "TANGENT" }, @@ -2049,6 +2068,10 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "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, "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, "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_SCALAR, "roughness", "ROUGHNESS" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "world", "WORLD_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "modelview", "MODELVIEW_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "camera", "CAMERA_MATRIX" }, @@ -2059,7 +2082,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "viewport_size", "vec3(VIEWPORT_SIZE, 0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_BOOLEAN, "output_is_srgb", "OUTPUT_IS_SRGB" }, - // Spatial, Fragment + // Node3D, Fragment { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "fragcoord", "FRAGCOORD.xyz" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "vertex", "VERTEX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "NORMAL" }, @@ -2072,7 +2095,6 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "point_coord", "vec3(POINT_COORD, 0.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "screen_uv", "vec3(SCREEN_UV, 0.0)" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "side", "float(FRONT_FACING ? 1.0 : 0.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_TRANSFORM, "world", "WORLD_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_TRANSFORM, "inv_camera", "INV_CAMERA_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_TRANSFORM, "camera", "CAMERA_MATRIX" }, @@ -2083,11 +2105,14 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_BOOLEAN, "output_is_srgb", "OUTPUT_IS_SRGB" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_BOOLEAN, "front_facing", "FRONT_FACING" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SAMPLER, "screen_texture", "SCREEN_TEXTURE" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SAMPLER, "normal_roughness_texture", "NORMAL_ROUGHNESS_TEXTURE" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SAMPLER, "depth_texture", "DEPTH_TEXTURE" }, - // Spatial, Light + // Node3D, Light { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "fragcoord", "FRAGCOORD.xyz" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "NORMAL" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV, 0.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "uv2", "vec3(UV2, 0.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "view", "VIEW" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light", "LIGHT" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light_color", "LIGHT_COLOR" }, @@ -2107,6 +2132,8 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "viewport_size", "vec3(VIEWPORT_SIZE, 0.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_BOOLEAN, "output_is_srgb", "OUTPUT_IS_SRGB" }, + // Canvas Item + // Canvas Item, Vertex { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "vertex", "vec3(VERTEX, 0.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV, 0.0)" }, @@ -2119,6 +2146,8 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "screen", "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, "instance_custom", "INSTANCE_CUSTOM.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "instance_custom_alpha", "INSTANCE_CUSTOM.a" }, // Canvas Item, Fragment { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "fragcoord", "FRAGCOORD.xyz" }, @@ -2137,6 +2166,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "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_SAMPLER, "specular_shininess_texture", "SPECULAR_SHININESS_TEXTURE" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "vertex", "vec3(VERTEX, 0.0)" }, // Canvas Item, Light { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "fragcoord", "FRAGCOORD.xyz" }, @@ -2150,7 +2180,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { 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, "light_position", "LIGHT_POSITION" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light_vertex", "LIGHT_VERTEX" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "shadow_color", "SHADOW_MODULATE.rgb" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "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, "screen_uv", "vec3(SCREEN_UV, 0.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "texture_pixel_size", "vec3(TEXTURE_PIXEL_SIZE, 1.0)" }, @@ -2366,7 +2396,7 @@ String VisualShaderNodeInput::generate_code(Shader::Mode p_mode, VisualShader::T while (preview_ports[idx].mode != Shader::MODE_MAX) { if (preview_ports[idx].mode == shader_mode && preview_ports[idx].shader_type == shader_type && preview_ports[idx].name == input_name) { - code = "\t" + p_output_vars[0] + " = " + preview_ports[idx].string + ";\n"; + code = " " + p_output_vars[0] + " = " + preview_ports[idx].string + ";\n"; break; } idx++; @@ -2375,19 +2405,19 @@ String VisualShaderNodeInput::generate_code(Shader::Mode p_mode, VisualShader::T if (code == String()) { switch (get_output_port_type(0)) { case PORT_TYPE_SCALAR: { - code = "\t" + p_output_vars[0] + " = 0.0;\n"; + code = " " + p_output_vars[0] + " = 0.0;\n"; } break; case PORT_TYPE_SCALAR_INT: { - code = "\t" + p_output_vars[0] + " = 0;\n"; + code = " " + p_output_vars[0] + " = 0;\n"; } break; case PORT_TYPE_VECTOR: { - code = "\t" + p_output_vars[0] + " = vec3(0.0);\n"; + code = " " + p_output_vars[0] + " = vec3(0.0);\n"; } break; case PORT_TYPE_TRANSFORM: { - code = "\t" + p_output_vars[0] + " = mat4(vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; + code = " " + p_output_vars[0] + " = mat4(vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; } break; case PORT_TYPE_BOOLEAN: { - code = "\t" + p_output_vars[0] + " = false;\n"; + code = " " + p_output_vars[0] + " = false;\n"; } break; default: //default (none found) is scalar break; @@ -2403,14 +2433,14 @@ String VisualShaderNodeInput::generate_code(Shader::Mode p_mode, VisualShader::T while (ports[idx].mode != Shader::MODE_MAX) { if (ports[idx].mode == shader_mode && ports[idx].shader_type == shader_type && ports[idx].name == input_name) { - code = "\t" + p_output_vars[0] + " = " + ports[idx].string + ";\n"; + code = " " + p_output_vars[0] + " = " + ports[idx].string + ";\n"; break; } idx++; } if (code == String()) { - code = "\t" + p_output_vars[0] + " = 0.0;\n"; //default (none found) is scalar + code = " " + p_output_vars[0] + " = 0.0;\n"; //default (none found) is scalar } return code; @@ -2422,7 +2452,7 @@ void VisualShaderNodeInput::set_input_name(String p_name) { input_name = p_name; emit_changed(); if (get_input_type_by_name(input_name) != prev_type) { - emit_signal("input_type_changed"); + emit_signal(SNAME("input_type_changed")); } } @@ -2566,8 +2596,8 @@ void VisualShaderNodeUniformRef::clear_uniforms() { } bool VisualShaderNodeUniformRef::has_uniform(const String &p_name) { - for (List<VisualShaderNodeUniformRef::Uniform>::Element *E = uniforms.front(); E; E = E->next()) { - if (E->get().name == p_name) { + for (const VisualShaderNodeUniformRef::Uniform &E : uniforms) { + if (E.name == p_name) { return true; } } @@ -2708,24 +2738,46 @@ VisualShaderNodeUniformRef::UniformType VisualShaderNodeUniformRef::get_uniform_ 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) { + case UniformType::UNIFORM_TYPE_FLOAT: + return PORT_TYPE_SCALAR; + case UniformType::UNIFORM_TYPE_INT: + return PORT_TYPE_SCALAR_INT; + case UniformType::UNIFORM_TYPE_SAMPLER: + return PORT_TYPE_SAMPLER; + case UniformType::UNIFORM_TYPE_VECTOR: + return PORT_TYPE_VECTOR; + case UniformType::UNIFORM_TYPE_TRANSFORM: + return PORT_TYPE_TRANSFORM; + case UniformType::UNIFORM_TYPE_COLOR: + return PORT_TYPE_VECTOR; + default: + break; + } + } + return PORT_TYPE_SCALAR; +} + String VisualShaderNodeUniformRef::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 { switch (uniform_type) { case UniformType::UNIFORM_TYPE_FLOAT: if (uniform_name == "[None]") { - return "\t" + p_output_vars[0] + " = 0.0;\n"; + return " " + p_output_vars[0] + " = 0.0;\n"; } - return "\t" + p_output_vars[0] + " = " + get_uniform_name() + ";\n"; + return " " + p_output_vars[0] + " = " + get_uniform_name() + ";\n"; case UniformType::UNIFORM_TYPE_INT: - return "\t" + p_output_vars[0] + " = " + get_uniform_name() + ";\n"; + return " " + p_output_vars[0] + " = " + get_uniform_name() + ";\n"; case UniformType::UNIFORM_TYPE_BOOLEAN: - return "\t" + p_output_vars[0] + " = " + get_uniform_name() + ";\n"; + return " " + p_output_vars[0] + " = " + get_uniform_name() + ";\n"; case UniformType::UNIFORM_TYPE_VECTOR: - return "\t" + p_output_vars[0] + " = " + get_uniform_name() + ";\n"; + return " " + p_output_vars[0] + " = " + get_uniform_name() + ";\n"; case UniformType::UNIFORM_TYPE_TRANSFORM: - return "\t" + p_output_vars[0] + " = " + get_uniform_name() + ";\n"; + return " " + p_output_vars[0] + " = " + get_uniform_name() + ";\n"; case UniformType::UNIFORM_TYPE_COLOR: { - String code = "\t" + p_output_vars[0] + " = " + get_uniform_name() + ".rgb;\n"; - code += "\t" + p_output_vars[1] + " = " + get_uniform_name() + ".a;\n"; + String code = " " + p_output_vars[0] + " = " + get_uniform_name() + ".rgb;\n"; + code += " " + p_output_vars[1] + " = " + get_uniform_name() + ".a;\n"; return code; } break; case UniformType::UNIFORM_TYPE_SAMPLER: @@ -2768,7 +2820,11 @@ VisualShaderNodeUniformRef::VisualShaderNodeUniformRef() { //////////////////////////////////////////// const VisualShaderNodeOutput::Port VisualShaderNodeOutput::ports[] = { - // Spatial, Vertex + //////////////////////////////////////////////////////////////////////// + // Node3D. + //////////////////////////////////////////////////////////////////////// + // Node3D, Vertex. + //////////////////////////////////////////////////////////////////////// { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "vertex", "VERTEX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "NORMAL" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "tangent", "TANGENT" }, @@ -2779,9 +2835,9 @@ const VisualShaderNodeOutput::Port VisualShaderNodeOutput::ports[] = { { 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_TRANSFORM, "model_view_matrix", "MODELVIEW_MATRIX" }, - - // Spatial, Fragment - + //////////////////////////////////////////////////////////////////////// + // Node3D, Fragment. + //////////////////////////////////////////////////////////////////////// { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "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" }, @@ -2805,29 +2861,48 @@ const VisualShaderNodeOutput::Port VisualShaderNodeOutput::ports[] = { { 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, "ao_light_affect", "AO_LIGHT_AFFECT" }, - - // Spatial, Light + //////////////////////////////////////////////////////////////////////// + // Node3D, Light. + //////////////////////////////////////////////////////////////////////// { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "diffuse", "DIFFUSE_LIGHT" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "specular", "SPECULAR_LIGHT" }, - // Canvas Item, Vertex + { 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, "vertex", "VERTEX:xy" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "UV:xy" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, - // Canvas Item, Fragment + { 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, "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, "normal", "NORMAL" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "normal_map", "NORMAL_MAP" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "normal_map_depth", "NORMAL_MAP_DEPTH" }, - // Canvas Item, Light + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "light_vertex", "LIGHT_VERTEX" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "shadow_vertex", "SHADOW_VERTEX:xy" }, + //////////////////////////////////////////////////////////////////////// + // Canvas Item, Light. + //////////////////////////////////////////////////////////////////////// { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light", "LIGHT.rgb" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "light_alpha", "LIGHT.a" }, - // Sky, Sky + //////////////////////////////////////////////////////////////////////// + // Sky, Sky. + //////////////////////////////////////////////////////////////////////// { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "ALPHA" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "fog", "FOG.rgb" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "fog_alpha", "FOG.a" }, + //////////////////////////////////////////////////////////////////////// { Shader::MODE_MAX, VisualShader::TYPE_MAX, VisualShaderNode::PORT_TYPE_TRANSFORM, nullptr, nullptr }, }; @@ -2921,9 +2996,9 @@ String VisualShaderNodeOutput::generate_code(Shader::Mode p_mode, VisualShader:: if (p_input_vars[count] != String()) { String s = ports[idx].string; if (s.find(":") != -1) { - code += "\t" + s.get_slicec(':', 0) + " = " + p_input_vars[count] + "." + s.get_slicec(':', 1) + ";\n"; + code += " " + s.get_slicec(':', 0) + " = " + p_input_vars[count] + "." + s.get_slicec(':', 1) + ";\n"; } else { - code += "\t" + s + " = " + p_input_vars[count] + ";\n"; + code += " " + s + " = " + p_input_vars[count] + ";\n"; } } count++; @@ -2941,7 +3016,7 @@ VisualShaderNodeOutput::VisualShaderNodeOutput() { void VisualShaderNodeUniform::set_uniform_name(const String &p_name) { uniform_name = p_name; - emit_signal("name_changed"); + emit_signal(SNAME("name_changed")); emit_changed(); } @@ -2950,6 +3025,10 @@ String VisualShaderNodeUniform::get_uniform_name() const { } void VisualShaderNodeUniform::set_qualifier(VisualShaderNodeUniform::Qualifier p_qual) { + ERR_FAIL_INDEX(int(p_qual), int(QUAL_MAX)); + if (qualifier == p_qual) { + return; + } qualifier = p_qual; emit_changed(); } @@ -2979,6 +3058,7 @@ void VisualShaderNodeUniform::_bind_methods() { BIND_ENUM_CONSTANT(QUAL_NONE); BIND_ENUM_CONSTANT(QUAL_GLOBAL); BIND_ENUM_CONSTANT(QUAL_INSTANCE); + BIND_ENUM_CONSTANT(QUAL_MAX); } String VisualShaderNodeUniform::_get_qual_str() const { @@ -2990,6 +3070,8 @@ String VisualShaderNodeUniform::_get_qual_str() const { return "global "; case QUAL_INSTANCE: return "instance "; + default: + break; } } return String(); @@ -2999,10 +3081,86 @@ String VisualShaderNodeUniform::get_warning(Shader::Mode p_mode, VisualShader::T List<String> keyword_list; ShaderLanguage::get_keyword_list(&keyword_list); if (keyword_list.find(uniform_name)) { - return TTR("Uniform name cannot be equal to a shader keyword. Choose another name."); + return TTR("Shader keywords cannot be used as uniform names.\nChoose another name."); } if (!is_qualifier_supported(qualifier)) { - return "This uniform type does not support that qualifier."; + String qualifier_str; + switch (qualifier) { + case QUAL_NONE: + break; + case QUAL_GLOBAL: + qualifier_str = "global"; + break; + case QUAL_INSTANCE: + qualifier_str = "instance"; + break; + default: + break; + } + return vformat(TTR("This uniform type does not support the '%s' qualifier."), qualifier_str); + } else if (qualifier == Qualifier::QUAL_GLOBAL) { + RS::GlobalVariableType gvt = RS::get_singleton()->global_variable_get_type(uniform_name); + if (gvt == RS::GLOBAL_VAR_TYPE_MAX) { + return vformat(TTR("Global uniform '%s' does not exist.\nCreate it in the Project Settings."), uniform_name); + } + bool incompatible_type = false; + switch (gvt) { + case RS::GLOBAL_VAR_TYPE_FLOAT: { + if (!Object::cast_to<VisualShaderNodeFloatUniform>(this)) { + incompatible_type = true; + } + } break; + case RS::GLOBAL_VAR_TYPE_INT: { + if (!Object::cast_to<VisualShaderNodeIntUniform>(this)) { + incompatible_type = true; + } + } break; + case RS::GLOBAL_VAR_TYPE_BOOL: { + if (!Object::cast_to<VisualShaderNodeBooleanUniform>(this)) { + incompatible_type = true; + } + } break; + case RS::GLOBAL_VAR_TYPE_COLOR: { + if (!Object::cast_to<VisualShaderNodeColorUniform>(this)) { + incompatible_type = true; + } + } break; + case RS::GLOBAL_VAR_TYPE_VEC3: { + if (!Object::cast_to<VisualShaderNodeVec3Uniform>(this)) { + incompatible_type = true; + } + } break; + case RS::GLOBAL_VAR_TYPE_TRANSFORM: { + if (!Object::cast_to<VisualShaderNodeTransformUniform>(this)) { + incompatible_type = true; + } + } break; + case RS::GLOBAL_VAR_TYPE_SAMPLER2D: { + if (!Object::cast_to<VisualShaderNodeTextureUniform>(this)) { + incompatible_type = true; + } + } break; + case RS::GLOBAL_VAR_TYPE_SAMPLER3D: { + if (!Object::cast_to<VisualShaderNodeTexture3DUniform>(this)) { + incompatible_type = true; + } + } break; + case RS::GLOBAL_VAR_TYPE_SAMPLER2DARRAY: { + if (!Object::cast_to<VisualShaderNodeTexture2DArrayUniform>(this)) { + incompatible_type = true; + } + } break; + case RS::GLOBAL_VAR_TYPE_SAMPLERCUBE: { + if (!Object::cast_to<VisualShaderNodeCubemapUniform>(this)) { + incompatible_type = true; + } + } break; + default: + break; + } + if (incompatible_type) { + return vformat(TTR("Global uniform '%s' has an incompatible type for this kind of node.\nChange it in the Project Settings."), uniform_name); + } } return String(); @@ -3194,6 +3352,10 @@ bool VisualShaderNodeGroupBase::is_valid_port_name(const String &p_name) const { } void VisualShaderNodeGroupBase::add_input_port(int p_id, int p_type, const String &p_name) { + ERR_FAIL_COND(has_input_port(p_id)); + ERR_FAIL_INDEX(p_type, int(PORT_TYPE_MAX)); + ERR_FAIL_COND(!is_valid_port_name(p_name)); + String str = itos(p_id) + "," + itos(p_type) + "," + p_name + ";"; Vector<String> inputs_strings = inputs.split(";", false); int index = 0; @@ -3266,6 +3428,10 @@ bool VisualShaderNodeGroupBase::has_input_port(int p_id) const { } void VisualShaderNodeGroupBase::add_output_port(int p_id, int p_type, const String &p_name) { + ERR_FAIL_COND(has_output_port(p_id)); + ERR_FAIL_INDEX(p_type, int(PORT_TYPE_MAX)); + ERR_FAIL_COND(!is_valid_port_name(p_name)); + String str = itos(p_id) + "," + itos(p_type) + "," + p_name + ";"; Vector<String> outputs_strings = outputs.split(";", false); int index = 0; @@ -3347,7 +3513,7 @@ void VisualShaderNodeGroupBase::clear_output_ports() { void VisualShaderNodeGroupBase::set_input_port_type(int p_id, int p_type) { ERR_FAIL_COND(!has_input_port(p_id)); - ERR_FAIL_COND(p_type < 0 || p_type >= PORT_TYPE_MAX); + ERR_FAIL_INDEX(p_type, int(PORT_TYPE_MAX)); if (input_ports[p_id].type == p_type) { return; @@ -3419,7 +3585,7 @@ String VisualShaderNodeGroupBase::get_input_port_name(int p_id) const { void VisualShaderNodeGroupBase::set_output_port_type(int p_id, int p_type) { ERR_FAIL_COND(!has_output_port(p_id)); - ERR_FAIL_COND(p_type < 0 || p_type >= PORT_TYPE_MAX); + ERR_FAIL_INDEX(p_type, int(PORT_TYPE_MAX)); if (output_ports[p_id].type == p_type) { return; @@ -3598,11 +3764,11 @@ String VisualShaderNodeExpression::generate_code(Shader::Mode p_mode, VisualShad String _expression = expression; _expression = _expression.insert(0, "\n"); - _expression = _expression.replace("\n", "\n\t\t"); + _expression = _expression.replace("\n", "\n "); static Vector<String> pre_symbols; if (pre_symbols.is_empty()) { - pre_symbols.push_back("\t"); + pre_symbols.push_back(" "); pre_symbols.push_back(","); pre_symbols.push_back(";"); pre_symbols.push_back("{"); @@ -3622,7 +3788,7 @@ String VisualShaderNodeExpression::generate_code(Shader::Mode p_mode, VisualShad static Vector<String> post_symbols; if (post_symbols.is_empty()) { - post_symbols.push_back("\t"); + post_symbols.push_back(" "); post_symbols.push_back("\n"); post_symbols.push_back(","); post_symbols.push_back(";"); @@ -3681,14 +3847,14 @@ String VisualShaderNodeExpression::generate_code(Shader::Mode p_mode, VisualShad default: continue; } - output_initializer += "\t" + p_output_vars[i] + " = " + tk + ";\n"; + output_initializer += " " + p_output_vars[i] + " = " + tk + ";\n"; } String code; code += output_initializer; - code += "\t{"; + code += " {"; code += _expression; - code += "\n\t}\n"; + code += "\n }\n"; return code; } diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index 454012b7ed..b3efac02aa 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -44,7 +44,7 @@ class VisualShader : public Shader { friend class VisualShaderNodeVersionChecker; - String version = ""; + Dictionary engine_version; public: enum Type { @@ -137,10 +137,12 @@ public: // internal methods Type get_shader_type() const; public: - void set_version(const String &p_version); - String get_version() const; + void set_engine_version(const Dictionary &p_version); + Dictionary get_engine_version() const; - void update_version(const String &p_new_version); +#ifndef DISABLE_DEPRECATED + void update_engine_version(const Dictionary &p_new_version); +#endif /* DISABLE_DEPRECATED */ enum { NODE_ID_INVALID = -1, @@ -312,6 +314,20 @@ protected: virtual void remove_input_port_default_value(int p_port) override; virtual void clear_default_input_values() override; + GDVIRTUAL0RC(String, _get_name) + GDVIRTUAL0RC(String, _get_description) + GDVIRTUAL0RC(String, _get_category) + GDVIRTUAL0RC(int, _get_return_icon_type) + GDVIRTUAL0RC(int, _get_input_port_count) + GDVIRTUAL1RC(int, _get_input_port_type, int) + GDVIRTUAL1RC(String, _get_input_port_name, int) + GDVIRTUAL0RC(int, _get_output_port_count) + GDVIRTUAL1RC(int, _get_output_port_type, int) + GDVIRTUAL1RC(String, _get_output_port_name, int) + GDVIRTUAL4RC(String, _get_code, Vector<String>, TypedArray<String>, int, int) + GDVIRTUAL1RC(String, _get_global_code, int) + GDVIRTUAL0RC(bool, _is_highend) + protected: void _set_input_port_default_value(int p_port, const Variant &p_value); @@ -433,6 +449,7 @@ public: QUAL_NONE, QUAL_GLOBAL, QUAL_INSTANCE, + QUAL_MAX, }; private: @@ -517,6 +534,7 @@ public: String get_uniform_name_by_index(int p_idx) const; UniformType get_uniform_type_by_name(const String &p_name) const; UniformType get_uniform_type_by_index(int p_idx) const; + PortType get_port_type_by_index(int p_idx) const; virtual Vector<StringName> get_editable_properties() const override; diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp index d3b094de31..e45dfdcb1b 100644 --- a/scene/resources/visual_shader_nodes.cpp +++ b/scene/resources/visual_shader_nodes.cpp @@ -38,7 +38,7 @@ VisualShaderNodeConstant::VisualShaderNodeConstant() { ////////////// Scalar(Float) String VisualShaderNodeFloatConstant::get_caption() const { - return "ScalarFloat"; + return "FloatConstant"; } int VisualShaderNodeFloatConstant::get_input_port_count() const { @@ -66,11 +66,14 @@ String VisualShaderNodeFloatConstant::get_output_port_name(int p_port) const { } String VisualShaderNodeFloatConstant::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 "\t" + p_output_vars[0] + " = " + vformat("%.6f", constant) + ";\n"; + return " " + p_output_vars[0] + " = " + vformat("%.6f", constant) + ";\n"; } -void VisualShaderNodeFloatConstant::set_constant(float p_value) { - constant = p_value; +void VisualShaderNodeFloatConstant::set_constant(float p_constant) { + if (Math::is_equal_approx(constant, p_constant)) { + return; + } + constant = p_constant; emit_changed(); } @@ -85,7 +88,7 @@ Vector<StringName> VisualShaderNodeFloatConstant::get_editable_properties() cons } void VisualShaderNodeFloatConstant::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_constant", "value"), &VisualShaderNodeFloatConstant::set_constant); + ClassDB::bind_method(D_METHOD("set_constant", "constant"), &VisualShaderNodeFloatConstant::set_constant); ClassDB::bind_method(D_METHOD("get_constant"), &VisualShaderNodeFloatConstant::get_constant); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "constant"), "set_constant", "get_constant"); @@ -97,7 +100,7 @@ VisualShaderNodeFloatConstant::VisualShaderNodeFloatConstant() { ////////////// Scalar(Int) String VisualShaderNodeIntConstant::get_caption() const { - return "ScalarInt"; + return "IntConstant"; } int VisualShaderNodeIntConstant::get_input_port_count() const { @@ -125,11 +128,14 @@ String VisualShaderNodeIntConstant::get_output_port_name(int p_port) const { } String VisualShaderNodeIntConstant::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 "\t" + p_output_vars[0] + " = " + itos(constant) + ";\n"; + return " " + p_output_vars[0] + " = " + itos(constant) + ";\n"; } -void VisualShaderNodeIntConstant::set_constant(int p_value) { - constant = p_value; +void VisualShaderNodeIntConstant::set_constant(int p_constant) { + if (constant == p_constant) { + return; + } + constant = p_constant; emit_changed(); } @@ -144,7 +150,7 @@ Vector<StringName> VisualShaderNodeIntConstant::get_editable_properties() const } void VisualShaderNodeIntConstant::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_constant", "value"), &VisualShaderNodeIntConstant::set_constant); + ClassDB::bind_method(D_METHOD("set_constant", "constant"), &VisualShaderNodeIntConstant::set_constant); ClassDB::bind_method(D_METHOD("get_constant"), &VisualShaderNodeIntConstant::get_constant); ADD_PROPERTY(PropertyInfo(Variant::INT, "constant"), "set_constant", "get_constant"); @@ -156,7 +162,7 @@ VisualShaderNodeIntConstant::VisualShaderNodeIntConstant() { ////////////// Boolean String VisualShaderNodeBooleanConstant::get_caption() const { - return "Boolean"; + return "BooleanConstant"; } int VisualShaderNodeBooleanConstant::get_input_port_count() const { @@ -184,11 +190,14 @@ String VisualShaderNodeBooleanConstant::get_output_port_name(int p_port) const { } String VisualShaderNodeBooleanConstant::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 "\t" + p_output_vars[0] + " = " + (constant ? "true" : "false") + ";\n"; + return " " + p_output_vars[0] + " = " + (constant ? "true" : "false") + ";\n"; } -void VisualShaderNodeBooleanConstant::set_constant(bool p_value) { - constant = p_value; +void VisualShaderNodeBooleanConstant::set_constant(bool p_constant) { + if (constant == p_constant) { + return; + } + constant = p_constant; emit_changed(); } @@ -203,7 +212,7 @@ Vector<StringName> VisualShaderNodeBooleanConstant::get_editable_properties() co } void VisualShaderNodeBooleanConstant::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_constant", "value"), &VisualShaderNodeBooleanConstant::set_constant); + ClassDB::bind_method(D_METHOD("set_constant", "constant"), &VisualShaderNodeBooleanConstant::set_constant); ClassDB::bind_method(D_METHOD("get_constant"), &VisualShaderNodeBooleanConstant::get_constant); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "constant"), "set_constant", "get_constant"); @@ -215,7 +224,7 @@ VisualShaderNodeBooleanConstant::VisualShaderNodeBooleanConstant() { ////////////// Color String VisualShaderNodeColorConstant::get_caption() const { - return "Color"; + return "ColorConstant"; } int VisualShaderNodeColorConstant::get_input_port_count() const { @@ -251,14 +260,17 @@ 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 += "\t" + p_output_vars[0] + " = " + vformat("vec3(%.6f, %.6f, %.6f)", constant.r, constant.g, constant.b) + ";\n"; - code += "\t" + p_output_vars[1] + " = " + vformat("%.6f", constant.a) + ";\n"; + 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; } -void VisualShaderNodeColorConstant::set_constant(Color p_value) { - constant = p_value; +void VisualShaderNodeColorConstant::set_constant(const Color &p_constant) { + if (constant.is_equal_approx(p_constant)) { + return; + } + constant = p_constant; emit_changed(); } @@ -273,7 +285,7 @@ Vector<StringName> VisualShaderNodeColorConstant::get_editable_properties() cons } void VisualShaderNodeColorConstant::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_constant", "value"), &VisualShaderNodeColorConstant::set_constant); + ClassDB::bind_method(D_METHOD("set_constant", "constant"), &VisualShaderNodeColorConstant::set_constant); ClassDB::bind_method(D_METHOD("get_constant"), &VisualShaderNodeColorConstant::get_constant); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "constant"), "set_constant", "get_constant"); @@ -285,7 +297,7 @@ VisualShaderNodeColorConstant::VisualShaderNodeColorConstant() { ////////////// Vector String VisualShaderNodeVec3Constant::get_caption() const { - return "Vector"; + return "VectorConstant"; } int VisualShaderNodeVec3Constant::get_input_port_count() const { @@ -313,11 +325,14 @@ String VisualShaderNodeVec3Constant::get_output_port_name(int p_port) const { } String VisualShaderNodeVec3Constant::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 "\t" + p_output_vars[0] + " = " + vformat("vec3(%.6f, %.6f, %.6f)", constant.x, constant.y, constant.z) + ";\n"; + return " " + p_output_vars[0] + " = " + vformat("vec3(%.6f, %.6f, %.6f)", constant.x, constant.y, constant.z) + ";\n"; } -void VisualShaderNodeVec3Constant::set_constant(Vector3 p_value) { - constant = p_value; +void VisualShaderNodeVec3Constant::set_constant(const Vector3 &p_constant) { + if (constant.is_equal_approx(p_constant)) { + return; + } + constant = p_constant; emit_changed(); } @@ -332,7 +347,7 @@ Vector<StringName> VisualShaderNodeVec3Constant::get_editable_properties() const } void VisualShaderNodeVec3Constant::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_constant", "value"), &VisualShaderNodeVec3Constant::set_constant); + ClassDB::bind_method(D_METHOD("set_constant", "constant"), &VisualShaderNodeVec3Constant::set_constant); ClassDB::bind_method(D_METHOD("get_constant"), &VisualShaderNodeVec3Constant::get_constant); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "constant"), "set_constant", "get_constant"); @@ -344,7 +359,7 @@ VisualShaderNodeVec3Constant::VisualShaderNodeVec3Constant() { ////////////// Transform3D String VisualShaderNodeTransformConstant::get_caption() const { - return "Transform3D"; + return "TransformConstant"; } int VisualShaderNodeTransformConstant::get_input_port_count() const { @@ -375,7 +390,7 @@ String VisualShaderNodeTransformConstant::generate_code(Shader::Mode p_mode, Vis Transform3D t = constant; t.basis.transpose(); - String code = "\t" + p_output_vars[0] + " = mat4("; + String code = " " + p_output_vars[0] + " = mat4("; code += vformat("vec4(%.6f, %.6f, %.6f, 0.0), ", t.basis[0].x, t.basis[0].y, t.basis[0].z); code += vformat("vec4(%.6f, %.6f, %.6f, 0.0), ", t.basis[1].x, t.basis[1].y, t.basis[1].z); code += vformat("vec4(%.6f, %.6f, %.6f, 0.0), ", t.basis[2].x, t.basis[2].y, t.basis[2].z); @@ -383,8 +398,11 @@ String VisualShaderNodeTransformConstant::generate_code(Shader::Mode p_mode, Vis return code; } -void VisualShaderNodeTransformConstant::set_constant(Transform3D p_value) { - constant = p_value; +void VisualShaderNodeTransformConstant::set_constant(const Transform3D &p_constant) { + if (constant.is_equal_approx(p_constant)) { + return; + } + constant = p_constant; emit_changed(); } @@ -399,7 +417,7 @@ Vector<StringName> VisualShaderNodeTransformConstant::get_editable_properties() } void VisualShaderNodeTransformConstant::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_constant", "value"), &VisualShaderNodeTransformConstant::set_constant); + ClassDB::bind_method(D_METHOD("set_constant", "constant"), &VisualShaderNodeTransformConstant::set_constant); ClassDB::bind_method(D_METHOD("get_constant"), &VisualShaderNodeTransformConstant::get_constant); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "constant"), "set_constant", "get_constant"); @@ -502,6 +520,8 @@ String VisualShaderNodeTexture::generate_global(Shader::Mode p_mode, VisualShade case TYPE_NORMAL_MAP: u += " : hint_normal"; break; + default: + break; } return u + ";\n"; } @@ -523,20 +543,20 @@ String VisualShaderNodeTexture::generate_code(Shader::Mode p_mode, VisualShader: if (p_input_vars[0] == String()) { // Use UV by default. if (p_input_vars[1] == String()) { - code += "\tvec4 " + id + "_read = texture(" + id + ", " + default_uv + ");\n"; + code += " vec4 " + id + "_read = texture(" + id + ", " + default_uv + ");\n"; } else { - code += "\tvec4 " + id + "_read = textureLod(" + id + ", " + default_uv + ", " + p_input_vars[1] + ");\n"; + code += " vec4 " + id + "_read = textureLod(" + id + ", " + default_uv + ", " + p_input_vars[1] + ");\n"; } } else if (p_input_vars[1] == String()) { //no lod - code += "\tvec4 " + id + "_read = texture(" + id + ", " + p_input_vars[0] + ".xy);\n"; + code += " vec4 " + id + "_read = texture(" + id + ", " + p_input_vars[0] + ".xy);\n"; } else { - code += "\tvec4 " + id + "_read = textureLod(" + id + ", " + p_input_vars[0] + ".xy, " + p_input_vars[1] + ");\n"; + code += " vec4 " + id + "_read = textureLod(" + id + ", " + p_input_vars[0] + ".xy, " + p_input_vars[1] + ");\n"; } - code += "\t" + p_output_vars[0] + " = " + id + "_read.rgb;\n"; - code += "\t" + p_output_vars[1] + " = " + id + "_read.a;\n"; + code += " " + p_output_vars[0] + " = " + id + "_read.rgb;\n"; + code += " " + p_output_vars[1] + " = " + id + "_read.a;\n"; return code; } @@ -544,98 +564,98 @@ String VisualShaderNodeTexture::generate_code(Shader::Mode p_mode, VisualShader: String id = p_input_vars[2]; String code; - code += "\t{\n"; + code += " {\n"; if (id == String()) { - code += "\t\tvec4 " + id + "_tex_read = vec4(0.0);\n"; + code += " vec4 " + id + "_tex_read = vec4(0.0);\n"; } else { if (p_input_vars[0] == String()) { // Use UV by default. if (p_input_vars[1] == String()) { - code += "\t\tvec4 " + id + "_tex_read = texture(" + id + ", " + default_uv + ");\n"; + code += " vec4 " + id + "_tex_read = texture(" + id + ", " + default_uv + ");\n"; } else { - code += "\t\tvec4 " + id + "_tex_read = textureLod(" + id + ", " + default_uv + ", " + p_input_vars[1] + ");\n"; + code += " vec4 " + id + "_tex_read = textureLod(" + id + ", " + default_uv + ", " + p_input_vars[1] + ");\n"; } } else if (p_input_vars[1] == String()) { //no lod - code += "\t\tvec4 " + id + "_tex_read = texture(" + id + ", " + p_input_vars[0] + ".xy);\n"; + code += " vec4 " + id + "_tex_read = texture(" + id + ", " + p_input_vars[0] + ".xy);\n"; } else { - code += "\t\tvec4 " + id + "_tex_read = textureLod(" + id + ", " + p_input_vars[0] + ".xy, " + p_input_vars[1] + ");\n"; + code += " vec4 " + id + "_tex_read = textureLod(" + id + ", " + p_input_vars[0] + ".xy, " + p_input_vars[1] + ");\n"; } - code += "\t\t" + p_output_vars[0] + " = " + id + "_tex_read.rgb;\n"; - code += "\t\t" + p_output_vars[1] + " = " + id + "_tex_read.a;\n"; + code += " " + p_output_vars[0] + " = " + id + "_tex_read.rgb;\n"; + code += " " + p_output_vars[1] + " = " + id + "_tex_read.a;\n"; } - code += "\t}\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 = "\t{\n"; + String code = " {\n"; if (p_input_vars[0] == String() || p_for_preview) { // Use UV by default. if (p_input_vars[1] == String()) { - code += "\t\tvec4 _tex_read = textureLod(SCREEN_TEXTURE, " + default_uv + ", 0.0 );\n"; + code += " vec4 _tex_read = textureLod(SCREEN_TEXTURE, " + default_uv + ", 0.0 );\n"; } else { - code += "\t\tvec4 _tex_read = textureLod(SCREEN_TEXTURE, " + default_uv + ", " + p_input_vars[1] + ");\n"; + code += " vec4 _tex_read = textureLod(SCREEN_TEXTURE, " + default_uv + ", " + p_input_vars[1] + ");\n"; } } else if (p_input_vars[1] == String()) { //no lod - code += "\t\tvec4 _tex_read = textureLod(SCREEN_TEXTURE, " + p_input_vars[0] + ".xy, 0.0);\n"; + code += " vec4 _tex_read = textureLod(SCREEN_TEXTURE, " + p_input_vars[0] + ".xy, 0.0);\n"; } else { - code += "\t\tvec4 _tex_read = textureLod(SCREEN_TEXTURE, " + p_input_vars[0] + ".xy, " + p_input_vars[1] + ");\n"; + code += " vec4 _tex_read = textureLod(SCREEN_TEXTURE, " + p_input_vars[0] + ".xy, " + p_input_vars[1] + ");\n"; } - code += "\t\t" + p_output_vars[0] + " = _tex_read.rgb;\n"; - code += "\t\t" + p_output_vars[1] + " = _tex_read.a;\n"; - code += "\t}\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 = "\t{\n"; + String code = " {\n"; if (p_input_vars[0] == String()) { // Use UV by default. if (p_input_vars[1] == String()) { - code += "\t\tvec4 _tex_read = texture(TEXTURE, " + default_uv + ");\n"; + code += " vec4 _tex_read = texture(TEXTURE, " + default_uv + ");\n"; } else { - code += "\t\tvec4 _tex_read = textureLod(TEXTURE, " + default_uv + ", " + p_input_vars[1] + ");\n"; + code += " vec4 _tex_read = textureLod(TEXTURE, " + default_uv + ", " + p_input_vars[1] + ");\n"; } } else if (p_input_vars[1] == String()) { //no lod - code += "\t\tvec4 _tex_read = texture(TEXTURE, " + p_input_vars[0] + ".xy);\n"; + code += " vec4 _tex_read = texture(TEXTURE, " + p_input_vars[0] + ".xy);\n"; } else { - code += "\t\tvec4 _tex_read = textureLod(TEXTURE, " + p_input_vars[0] + ".xy, " + p_input_vars[1] + ");\n"; + code += " vec4 _tex_read = textureLod(TEXTURE, " + p_input_vars[0] + ".xy, " + p_input_vars[1] + ");\n"; } - code += "\t\t" + p_output_vars[0] + " = _tex_read.rgb;\n"; - code += "\t\t" + p_output_vars[1] + " = _tex_read.a;\n"; - code += "\t}\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 = "\t{\n"; + String code = " {\n"; if (p_input_vars[0] == String()) { // Use UV by default. if (p_input_vars[1] == String()) { - code += "\t\tvec4 _tex_read = texture(NORMAL_TEXTURE, " + default_uv + ");\n"; + code += " vec4 _tex_read = texture(NORMAL_TEXTURE, " + default_uv + ");\n"; } else { - code += "\t\tvec4 _tex_read = textureLod(NORMAL_TEXTURE, " + default_uv + ", " + p_input_vars[1] + ");\n"; + code += " vec4 _tex_read = textureLod(NORMAL_TEXTURE, " + default_uv + ", " + p_input_vars[1] + ");\n"; } } else if (p_input_vars[1] == String()) { //no lod - code += "\t\tvec4 _tex_read = texture(NORMAL_TEXTURE, " + p_input_vars[0] + ".xy);\n"; + code += " vec4 _tex_read = texture(NORMAL_TEXTURE, " + p_input_vars[0] + ".xy);\n"; } else { - code += "\t\tvec4 _tex_read = textureLod(NORMAL_TEXTURE, " + p_input_vars[0] + ".xy, " + p_input_vars[1] + ");\n"; + code += " vec4 _tex_read = textureLod(NORMAL_TEXTURE, " + p_input_vars[0] + ".xy, " + p_input_vars[1] + ");\n"; } - code += "\t\t" + p_output_vars[0] + " = _tex_read.rgb;\n"; - code += "\t\t" + p_output_vars[1] + " = _tex_read.a;\n"; - code += "\t}\n"; + code += " " + p_output_vars[0] + " = _tex_read.rgb;\n"; + code += " " + p_output_vars[1] + " = _tex_read.a;\n"; + code += " }\n"; return code; } @@ -643,50 +663,53 @@ String VisualShaderNodeTexture::generate_code(Shader::Mode p_mode, VisualShader: { if (source == SOURCE_DEPTH) { String code; - code += "\t" + p_output_vars[0] + " = 0.0;\n"; - code += "\t" + p_output_vars[1] + " = 1.0;\n"; + 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 = "\t{\n"; + String code = " {\n"; if (p_input_vars[0] == String()) { // Use UV by default. if (p_input_vars[1] == String()) { - code += "\t\tfloat _depth = texture(DEPTH_TEXTURE, " + default_uv + ").r;\n"; + code += " float _depth = texture(DEPTH_TEXTURE, " + default_uv + ").r;\n"; } else { - code += "\t\tfloat _depth = textureLod(DEPTH_TEXTURE, " + default_uv + ", " + p_input_vars[1] + ").r;\n"; + code += " float _depth = textureLod(DEPTH_TEXTURE, " + default_uv + ", " + p_input_vars[1] + ").r;\n"; } } else if (p_input_vars[1] == String()) { //no lod - code += "\t\tfloat _depth = texture(DEPTH_TEXTURE, " + p_input_vars[0] + ".xy).r;\n"; + code += " float _depth = texture(DEPTH_TEXTURE, " + p_input_vars[0] + ".xy).r;\n"; } else { - code += "\t\tfloat _depth = textureLod(DEPTH_TEXTURE, " + p_input_vars[0] + ".xy, " + p_input_vars[1] + ").r;\n"; + code += " float _depth = textureLod(DEPTH_TEXTURE, " + p_input_vars[0] + ".xy, " + p_input_vars[1] + ").r;\n"; } - code += "\t\t" + p_output_vars[0] + " = _depth;\n"; - code += "\t\t" + p_output_vars[1] + " = 1.0;\n"; - code += "\t}\n"; + 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 += "\t" + p_output_vars[0] + " = 0.0;\n"; - code += "\t" + p_output_vars[1] + " = 1.0;\n"; + code += " " + p_output_vars[0] + " = 0.0;\n"; + code += " " + p_output_vars[1] + " = 1.0;\n"; return code; } //none String code; - code += "\t" + p_output_vars[0] + " = vec3(0.0);\n"; - code += "\t" + p_output_vars[1] + " = 1.0;\n"; + code += " " + p_output_vars[0] + " = vec3(0.0);\n"; + code += " " + p_output_vars[1] + " = 1.0;\n"; return code; } void VisualShaderNodeTexture::set_source(Source p_source) { - source = p_source; - switch (source) { + ERR_FAIL_INDEX(int(p_source), int(SOURCE_MAX)); + if (source == p_source) { + return; + } + switch (p_source) { case SOURCE_TEXTURE: simple_decl = true; break; @@ -705,17 +728,20 @@ void VisualShaderNodeTexture::set_source(Source p_source) { case SOURCE_PORT: simple_decl = false; break; + default: + break; } + source = p_source; emit_changed(); - emit_signal("editor_refresh_request"); + emit_signal(SNAME("editor_refresh_request")); } VisualShaderNodeTexture::Source VisualShaderNodeTexture::get_source() const { return source; } -void VisualShaderNodeTexture::set_texture(Ref<Texture2D> p_value) { - texture = p_value; +void VisualShaderNodeTexture::set_texture(Ref<Texture2D> p_texture) { + texture = p_texture; emit_changed(); } @@ -723,8 +749,12 @@ Ref<Texture2D> VisualShaderNodeTexture::get_texture() const { return texture; } -void VisualShaderNodeTexture::set_texture_type(TextureType p_type) { - texture_type = p_type; +void VisualShaderNodeTexture::set_texture_type(TextureType p_texture_type) { + ERR_FAIL_INDEX(int(p_texture_type), int(TYPE_MAX)); + if (texture_type == p_texture_type) { + return; + } + texture_type = p_texture_type; emit_changed(); } @@ -797,15 +827,18 @@ void VisualShaderNodeTexture::_bind_methods() { BIND_ENUM_CONSTANT(SOURCE_2D_NORMAL); BIND_ENUM_CONSTANT(SOURCE_DEPTH); BIND_ENUM_CONSTANT(SOURCE_PORT); + BIND_ENUM_CONSTANT(SOURCE_MAX); + BIND_ENUM_CONSTANT(TYPE_DATA); BIND_ENUM_CONSTANT(TYPE_COLOR); BIND_ENUM_CONSTANT(TYPE_NORMAL_MAP); + BIND_ENUM_CONSTANT(TYPE_MAX); } VisualShaderNodeTexture::VisualShaderNodeTexture() { } -////////////// Curve +////////////// CurveTexture String VisualShaderNodeCurveTexture::get_caption() const { return "CurveTexture"; @@ -856,11 +889,11 @@ String VisualShaderNodeCurveTexture::generate_global(Shader::Mode p_mode, Visual String VisualShaderNodeCurveTexture::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 (p_input_vars[0] == String()) { - return "\t" + p_output_vars[0] + " = 0.0;\n"; + return " " + p_output_vars[0] + " = 0.0;\n"; } String id = make_unique_id(p_type, p_id, "curve"); String code; - code += "\t" + p_output_vars[0] + " = texture(" + id + ", vec2(" + p_input_vars[0] + ")).r;\n"; + code += " " + p_output_vars[0] + " = texture(" + id + ", vec2(" + p_input_vars[0] + ")).r;\n"; return code; } @@ -889,6 +922,90 @@ VisualShaderNodeCurveTexture::VisualShaderNodeCurveTexture() { allow_v_resize = false; } +////////////// CurveXYZTexture + +String VisualShaderNodeCurveXYZTexture::get_caption() const { + return "CurveXYZTexture"; +} + +int VisualShaderNodeCurveXYZTexture::get_input_port_count() const { + return 1; +} + +VisualShaderNodeCurveXYZTexture::PortType VisualShaderNodeCurveXYZTexture::get_input_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeCurveXYZTexture::get_input_port_name(int p_port) const { + return String(); +} + +int VisualShaderNodeCurveXYZTexture::get_output_port_count() const { + return 1; +} + +VisualShaderNodeCurveXYZTexture::PortType VisualShaderNodeCurveXYZTexture::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeCurveXYZTexture::get_output_port_name(int p_port) const { + return String(); +} + +void VisualShaderNodeCurveXYZTexture::set_texture(Ref<CurveXYZTexture> p_texture) { + texture = p_texture; + emit_changed(); +} + +Ref<CurveXYZTexture> VisualShaderNodeCurveXYZTexture::get_texture() const { + return texture; +} + +Vector<StringName> VisualShaderNodeCurveXYZTexture::get_editable_properties() const { + Vector<StringName> props; + props.push_back("texture"); + return props; +} + +String VisualShaderNodeCurveXYZTexture::generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + return "uniform sampler2D " + make_unique_id(p_type, p_id, "curve3d") + ";\n"; +} + +String VisualShaderNodeCurveXYZTexture::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 (p_input_vars[0] == String()) { + return " " + p_output_vars[0] + " = vec3(0.0);\n"; + } + String id = make_unique_id(p_type, p_id, "curve3d"); + String code; + code += " " + p_output_vars[0] + " = texture(" + id + ", vec2(" + p_input_vars[0] + ")).rgb;\n"; + return code; +} + +Vector<VisualShader::DefaultTextureParam> VisualShaderNodeCurveXYZTexture::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const { + VisualShader::DefaultTextureParam dtp; + dtp.name = make_unique_id(p_type, p_id, "curve3d"); + dtp.param = texture; + Vector<VisualShader::DefaultTextureParam> ret; + ret.push_back(dtp); + return ret; +} + +void VisualShaderNodeCurveXYZTexture::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_texture", "texture"), &VisualShaderNodeCurveXYZTexture::set_texture); + ClassDB::bind_method(D_METHOD("get_texture"), &VisualShaderNodeCurveXYZTexture::get_texture); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "CurveXYZTexture"), "set_texture", "get_texture"); +} + +bool VisualShaderNodeCurveXYZTexture::is_use_prop_slots() const { + return true; +} + +VisualShaderNodeCurveXYZTexture::VisualShaderNodeCurveXYZTexture() { + simple_decl = true; + allow_v_resize = false; +} + ////////////// Sample3D int VisualShaderNodeSample3D::get_input_port_count() const { @@ -956,7 +1073,7 @@ String VisualShaderNodeSample3D::generate_code(Shader::Mode p_mode, VisualShader String code; if (source == SOURCE_TEXTURE || source == SOURCE_PORT) { String id; - code += "\t{\n"; + code += " {\n"; if (source == SOURCE_TEXTURE) { id = make_unique_id(p_type, p_id, "tex3d"); } else { @@ -965,34 +1082,38 @@ String VisualShaderNodeSample3D::generate_code(Shader::Mode p_mode, VisualShader if (id != String()) { if (p_input_vars[0] == String()) { // Use UV by default. if (p_input_vars[1] == String()) { - code += "\t\tvec4 " + id + "_tex_read = texture(" + id + ", " + default_uv + ");\n"; + code += " vec4 " + id + "_tex_read = texture(" + id + ", " + default_uv + ");\n"; } else { - code += "\t\tvec4 " + id + "_tex_read = textureLod(" + id + ", " + default_uv + ", " + p_input_vars[1] + ");\n"; + code += " vec4 " + id + "_tex_read = textureLod(" + id + ", " + default_uv + ", " + p_input_vars[1] + ");\n"; } } else if (p_input_vars[1] == String()) { //no lod - code += "\t\tvec4 " + id + "_tex_read = texture(" + id + ", " + p_input_vars[0] + ");\n"; + code += " vec4 " + id + "_tex_read = texture(" + id + ", " + p_input_vars[0] + ");\n"; } else { - code += "\t\tvec4 " + id + "_tex_read = textureLod(" + id + ", " + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; + code += " vec4 " + id + "_tex_read = textureLod(" + id + ", " + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; } } else { - code += "\t\tvec4 " + id + "_tex_read = vec4(0.0);\n"; + code += " vec4 " + id + "_tex_read = vec4(0.0);\n"; } - code += "\t\t" + p_output_vars[0] + " = " + id + "_tex_read.rgb;\n"; - code += "\t\t" + p_output_vars[1] + " = " + id + "_tex_read.a;\n"; - code += "\t}\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 += "\t" + p_output_vars[0] + " = vec3(0.0);\n"; - code += "\t" + p_output_vars[1] + " = 1.0;\n"; + code += " " + p_output_vars[0] + " = vec3(0.0);\n"; + code += " " + p_output_vars[1] + " = 1.0;\n"; return code; } void VisualShaderNodeSample3D::set_source(Source p_source) { + ERR_FAIL_INDEX(int(p_source), int(SOURCE_MAX)); + if (source == p_source) { + return; + } source = p_source; emit_changed(); - emit_signal("editor_refresh_request"); + emit_signal(SNAME("editor_refresh_request")); } VisualShaderNodeSample3D::Source VisualShaderNodeSample3D::get_source() const { @@ -1007,6 +1128,7 @@ void VisualShaderNodeSample3D::_bind_methods() { BIND_ENUM_CONSTANT(SOURCE_TEXTURE); BIND_ENUM_CONSTANT(SOURCE_PORT); + BIND_ENUM_CONSTANT(SOURCE_MAX); } String VisualShaderNodeSample3D::get_warning(Shader::Mode p_mode, VisualShader::Type p_type) const { @@ -1043,7 +1165,7 @@ String VisualShaderNodeTexture2DArray::get_input_port_name(int p_port) const { Vector<VisualShader::DefaultTextureParam> VisualShaderNodeTexture2DArray::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const { VisualShader::DefaultTextureParam dtp; dtp.name = make_unique_id(p_type, p_id, "tex3d"); - dtp.param = texture; + dtp.param = texture_array; Vector<VisualShader::DefaultTextureParam> ret; ret.push_back(dtp); return ret; @@ -1056,13 +1178,13 @@ String VisualShaderNodeTexture2DArray::generate_global(Shader::Mode p_mode, Visu return String(); } -void VisualShaderNodeTexture2DArray::set_texture_array(Ref<Texture2DArray> p_value) { - texture = p_value; +void VisualShaderNodeTexture2DArray::set_texture_array(Ref<Texture2DArray> p_texture_array) { + texture_array = p_texture_array; emit_changed(); } Ref<Texture2DArray> VisualShaderNodeTexture2DArray::get_texture_array() const { - return texture; + return texture_array; } Vector<StringName> VisualShaderNodeTexture2DArray::get_editable_properties() const { @@ -1113,8 +1235,8 @@ String VisualShaderNodeTexture3D::generate_global(Shader::Mode p_mode, VisualSha return String(); } -void VisualShaderNodeTexture3D::set_texture(Ref<Texture3D> p_value) { - texture = p_value; +void VisualShaderNodeTexture3D::set_texture(Ref<Texture3D> p_texture) { + texture = p_texture; emit_changed(); } @@ -1217,6 +1339,8 @@ String VisualShaderNodeCubemap::generate_global(Shader::Mode p_mode, VisualShade case TYPE_NORMAL_MAP: u += " : hint_normal"; break; + default: + break; } return u + ";\n"; } @@ -1241,33 +1365,33 @@ String VisualShaderNodeCubemap::generate_code(Shader::Mode p_mode, VisualShader: return String(); } - code += "\t{\n"; + code += " {\n"; if (id == String()) { - code += "\t\tvec4 " + id + "_read = vec4(0.0);\n"; - code += "\t\t" + p_output_vars[0] + " = " + id + "_read.rgb;\n"; - code += "\t\t" + p_output_vars[1] + " = " + id + "_read.a;\n"; - code += "\t}\n"; + 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"; return code; } if (p_input_vars[0] == String()) { // Use UV by default. if (p_input_vars[1] == String()) { - code += "\t\tvec4 " + id + "_read = texture(" + id + ", " + default_uv + ");\n"; + code += " vec4 " + id + "_read = texture(" + id + ", " + default_uv + ");\n"; } else { - code += "\t\tvec4 " + id + "_read = textureLod(" + id + ", " + default_uv + ", " + p_input_vars[1] + " );\n"; + code += " vec4 " + id + "_read = textureLod(" + id + ", " + default_uv + ", " + p_input_vars[1] + " );\n"; } } else if (p_input_vars[1] == String()) { //no lod - code += "\t\tvec4 " + id + "_read = texture(" + id + ", " + p_input_vars[0] + ");\n"; + code += " vec4 " + id + "_read = texture(" + id + ", " + p_input_vars[0] + ");\n"; } else { - code += "\t\tvec4 " + id + "_read = textureLod(" + id + ", " + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; + code += " vec4 " + id + "_read = textureLod(" + id + ", " + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; } - code += "\t\t" + p_output_vars[0] + " = " + id + "_read.rgb;\n"; - code += "\t\t" + p_output_vars[1] + " = " + id + "_read.a;\n"; - code += "\t}\n"; + code += " " + p_output_vars[0] + " = " + id + "_read.rgb;\n"; + code += " " + p_output_vars[1] + " = " + id + "_read.a;\n"; + code += " }\n"; return code; } @@ -1280,17 +1404,21 @@ String VisualShaderNodeCubemap::get_input_port_default_hint(int p_port) const { } void VisualShaderNodeCubemap::set_source(Source p_source) { + ERR_FAIL_INDEX(int(p_source), int(SOURCE_MAX)); + if (source == p_source) { + return; + } source = p_source; emit_changed(); - emit_signal("editor_refresh_request"); + emit_signal(SNAME("editor_refresh_request")); } VisualShaderNodeCubemap::Source VisualShaderNodeCubemap::get_source() const { return source; } -void VisualShaderNodeCubemap::set_cube_map(Ref<Cubemap> p_value) { - cube_map = p_value; +void VisualShaderNodeCubemap::set_cube_map(Ref<Cubemap> p_cube_map) { + cube_map = p_cube_map; emit_changed(); } @@ -1298,8 +1426,12 @@ Ref<Cubemap> VisualShaderNodeCubemap::get_cube_map() const { return cube_map; } -void VisualShaderNodeCubemap::set_texture_type(TextureType p_type) { - texture_type = p_type; +void VisualShaderNodeCubemap::set_texture_type(TextureType p_texture_type) { + ERR_FAIL_INDEX(int(p_texture_type), int(TYPE_MAX)); + if (texture_type == p_texture_type) { + return; + } + texture_type = p_texture_type; emit_changed(); } @@ -1340,10 +1472,12 @@ void VisualShaderNodeCubemap::_bind_methods() { BIND_ENUM_CONSTANT(SOURCE_TEXTURE); BIND_ENUM_CONSTANT(SOURCE_PORT); + BIND_ENUM_CONSTANT(SOURCE_MAX); BIND_ENUM_CONSTANT(TYPE_DATA); BIND_ENUM_CONSTANT(TYPE_COLOR); BIND_ENUM_CONSTANT(TYPE_NORMAL_MAP); + BIND_ENUM_CONSTANT(TYPE_MAX); } VisualShaderNodeCubemap::VisualShaderNodeCubemap() { @@ -1381,7 +1515,7 @@ String VisualShaderNodeFloatOp::get_output_port_name(int p_port) const { } String VisualShaderNodeFloatOp::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 = "\t" + p_output_vars[0] + " = "; + String code = " " + p_output_vars[0] + " = "; switch (op) { case OP_ADD: code += p_input_vars[0] + " + " + p_input_vars[1] + ";\n"; @@ -1413,12 +1547,17 @@ String VisualShaderNodeFloatOp::generate_code(Shader::Mode p_mode, VisualShader: case OP_STEP: code += "step(" + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; break; + default: + break; } - return code; } void VisualShaderNodeFloatOp::set_operator(Operator p_op) { + ERR_FAIL_INDEX(int(p_op), int(OP_ENUM_SIZE)); + if (op == p_op) { + return; + } op = p_op; emit_changed(); } @@ -1449,6 +1588,7 @@ void VisualShaderNodeFloatOp::_bind_methods() { BIND_ENUM_CONSTANT(OP_MIN); BIND_ENUM_CONSTANT(OP_ATAN2); BIND_ENUM_CONSTANT(OP_STEP); + BIND_ENUM_CONSTANT(OP_ENUM_SIZE); } VisualShaderNodeFloatOp::VisualShaderNodeFloatOp() { @@ -1487,7 +1627,7 @@ String VisualShaderNodeIntOp::get_output_port_name(int p_port) const { } String VisualShaderNodeIntOp::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 = "\t" + p_output_vars[0] + " = "; + String code = " " + p_output_vars[0] + " = "; switch (op) { case OP_ADD: code += p_input_vars[0] + " + " + p_input_vars[1] + ";\n"; @@ -1510,12 +1650,18 @@ String VisualShaderNodeIntOp::generate_code(Shader::Mode p_mode, VisualShader::T case OP_MIN: code += "min(" + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; break; + default: + break; } return code; } void VisualShaderNodeIntOp::set_operator(Operator p_op) { + ERR_FAIL_INDEX(int(p_op), OP_ENUM_SIZE); + if (op == p_op) { + return; + } op = p_op; emit_changed(); } @@ -1543,6 +1689,7 @@ void VisualShaderNodeIntOp::_bind_methods() { BIND_ENUM_CONSTANT(OP_MOD); BIND_ENUM_CONSTANT(OP_MAX); BIND_ENUM_CONSTANT(OP_MIN); + BIND_ENUM_CONSTANT(OP_ENUM_SIZE); } VisualShaderNodeIntOp::VisualShaderNodeIntOp() { @@ -1581,7 +1728,7 @@ String VisualShaderNodeVectorOp::get_output_port_name(int p_port) const { } String VisualShaderNodeVectorOp::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 = "\t" + p_output_vars[0] + " = "; + String code = " " + p_output_vars[0] + " = "; switch (op) { case OP_ADD: code += p_input_vars[0] + " + " + p_input_vars[1] + ";\n"; @@ -1619,12 +1766,18 @@ String VisualShaderNodeVectorOp::generate_code(Shader::Mode p_mode, VisualShader case OP_STEP: code += "step(" + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; break; + default: + break; } return code; } void VisualShaderNodeVectorOp::set_operator(Operator p_op) { + ERR_FAIL_INDEX(int(p_op), int(OP_ENUM_SIZE)); + if (op == p_op) { + return; + } op = p_op; emit_changed(); } @@ -1657,6 +1810,7 @@ void VisualShaderNodeVectorOp::_bind_methods() { BIND_ENUM_CONSTANT(OP_ATAN2); BIND_ENUM_CONSTANT(OP_REFLECT); BIND_ENUM_CONSTANT(OP_STEP); + BIND_ENUM_CONSTANT(OP_ENUM_SIZE); } VisualShaderNodeVectorOp::VisualShaderNodeVectorOp() { @@ -1699,75 +1853,80 @@ String VisualShaderNodeColorOp::generate_code(Shader::Mode p_mode, VisualShader: static const char *axisn[3] = { "x", "y", "z" }; switch (op) { case OP_SCREEN: { - code += "\t" + p_output_vars[0] + " = vec3(1.0) - (vec3(1.0) - " + p_input_vars[0] + ") * (vec3(1.0) - " + p_input_vars[1] + ");\n"; + code += " " + p_output_vars[0] + " = vec3(1.0) - (vec3(1.0) - " + p_input_vars[0] + ") * (vec3(1.0) - " + p_input_vars[1] + ");\n"; } break; case OP_DIFFERENCE: { - code += "\t" + p_output_vars[0] + " = abs(" + p_input_vars[0] + " - " + p_input_vars[1] + ");\n"; + code += " " + p_output_vars[0] + " = abs(" + p_input_vars[0] + " - " + p_input_vars[1] + ");\n"; } break; case OP_DARKEN: { - code += "\t" + p_output_vars[0] + " = min(" + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; + code += " " + p_output_vars[0] + " = min(" + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; } break; case OP_LIGHTEN: { - code += "\t" + p_output_vars[0] + " = max(" + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; + code += " " + p_output_vars[0] + " = max(" + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; } break; case OP_OVERLAY: { for (int i = 0; i < 3; i++) { - code += "\t{\n"; - code += "\t\tfloat base = " + p_input_vars[0] + "." + axisn[i] + ";\n"; - code += "\t\tfloat blend = " + p_input_vars[1] + "." + axisn[i] + ";\n"; - code += "\t\tif (base < 0.5) {\n"; - code += "\t\t\t" + p_output_vars[0] + "." + axisn[i] + " = 2.0 * base * blend;\n"; - code += "\t\t} else {\n"; - code += "\t\t\t" + p_output_vars[0] + "." + axisn[i] + " = 1.0 - 2.0 * (1.0 - blend) * (1.0 - base);\n"; - code += "\t\t}\n"; - code += "\t}\n"; + code += " {\n"; + code += " float base = " + p_input_vars[0] + "." + axisn[i] + ";\n"; + code += " float blend = " + p_input_vars[1] + "." + axisn[i] + ";\n"; + code += " if (base < 0.5) {\n"; + code += " " + p_output_vars[0] + "." + axisn[i] + " = 2.0 * base * blend;\n"; + code += " } else {\n"; + code += " " + p_output_vars[0] + "." + axisn[i] + " = 1.0 - 2.0 * (1.0 - blend) * (1.0 - base);\n"; + code += " }\n"; + code += " }\n"; } } break; case OP_DODGE: { - code += "\t" + p_output_vars[0] + " = (" + p_input_vars[0] + ") / (vec3(1.0) - " + p_input_vars[1] + ");\n"; + code += " " + p_output_vars[0] + " = (" + p_input_vars[0] + ") / (vec3(1.0) - " + p_input_vars[1] + ");\n"; } break; case OP_BURN: { - code += "\t" + p_output_vars[0] + " = vec3(1.0) - (vec3(1.0) - " + p_input_vars[0] + ") / (" + p_input_vars[1] + ");\n"; + code += " " + p_output_vars[0] + " = vec3(1.0) - (vec3(1.0) - " + p_input_vars[0] + ") / (" + p_input_vars[1] + ");\n"; } break; case OP_SOFT_LIGHT: { for (int i = 0; i < 3; i++) { - code += "\t{\n"; - code += "\t\tfloat base = " + p_input_vars[0] + "." + axisn[i] + ";\n"; - code += "\t\tfloat blend = " + p_input_vars[1] + "." + axisn[i] + ";\n"; - code += "\t\tif (base < 0.5) {\n"; - code += "\t\t\t" + p_output_vars[0] + "." + axisn[i] + " = (base * (blend + 0.5));\n"; - code += "\t\t} else {\n"; - code += "\t\t\t" + p_output_vars[0] + "." + axisn[i] + " = (1.0 - (1.0 - base) * (1.0 - (blend - 0.5)));\n"; - code += "\t\t}\n"; - code += "\t}\n"; + code += " {\n"; + code += " float base = " + p_input_vars[0] + "." + axisn[i] + ";\n"; + code += " float blend = " + p_input_vars[1] + "." + axisn[i] + ";\n"; + code += " if (base < 0.5) {\n"; + code += " " + p_output_vars[0] + "." + axisn[i] + " = (base * (blend + 0.5));\n"; + code += " } else {\n"; + code += " " + p_output_vars[0] + "." + axisn[i] + " = (1.0 - (1.0 - base) * (1.0 - (blend - 0.5)));\n"; + code += " }\n"; + code += " }\n"; } } break; case OP_HARD_LIGHT: { for (int i = 0; i < 3; i++) { - code += "\t{\n"; - code += "\t\tfloat base = " + p_input_vars[0] + "." + axisn[i] + ";\n"; - code += "\t\tfloat blend = " + p_input_vars[1] + "." + axisn[i] + ";\n"; - code += "\t\tif (base < 0.5) {\n"; - code += "\t\t\t" + p_output_vars[0] + "." + axisn[i] + " = (base * (2.0 * blend));\n"; - code += "\t\t} else {\n"; - code += "\t\t\t" + p_output_vars[0] + "." + axisn[i] + " = (1.0 - (1.0 - base) * (1.0 - 2.0 * (blend - 0.5)));\n"; - code += "\t\t}\n"; - code += "\t}\n"; + code += " {\n"; + code += " float base = " + p_input_vars[0] + "." + axisn[i] + ";\n"; + code += " float blend = " + p_input_vars[1] + "." + axisn[i] + ";\n"; + code += " if (base < 0.5) {\n"; + code += " " + p_output_vars[0] + "." + axisn[i] + " = (base * (2.0 * blend));\n"; + code += " } else {\n"; + code += " " + p_output_vars[0] + "." + axisn[i] + " = (1.0 - (1.0 - base) * (1.0 - 2.0 * (blend - 0.5)));\n"; + code += " }\n"; + code += " }\n"; } } break; + default: + break; } return code; } void VisualShaderNodeColorOp::set_operator(Operator p_op) { - op = p_op; - switch (op) { + ERR_FAIL_INDEX(int(p_op), int(OP_MAX)); + if (op == p_op) { + return; + } + switch (p_op) { case OP_SCREEN: simple_decl = true; break; @@ -1795,7 +1954,10 @@ void VisualShaderNodeColorOp::set_operator(Operator p_op) { case OP_HARD_LIGHT: simple_decl = false; break; + default: + break; } + op = p_op; emit_changed(); } @@ -1824,6 +1986,7 @@ void VisualShaderNodeColorOp::_bind_methods() { BIND_ENUM_CONSTANT(OP_BURN); BIND_ENUM_CONSTANT(OP_SOFT_LIGHT); BIND_ENUM_CONSTANT(OP_HARD_LIGHT); + BIND_ENUM_CONSTANT(OP_MAX); } VisualShaderNodeColorOp::VisualShaderNodeColorOp() { @@ -1831,76 +1994,99 @@ VisualShaderNodeColorOp::VisualShaderNodeColorOp() { set_input_port_default_value(1, Vector3()); } -////////////// Transform Mult +////////////// Transform Op -String VisualShaderNodeTransformMult::get_caption() const { - return "TransformMult"; +String VisualShaderNodeTransformOp::get_caption() const { + return "TransformOp"; } -int VisualShaderNodeTransformMult::get_input_port_count() const { +int VisualShaderNodeTransformOp::get_input_port_count() const { return 2; } -VisualShaderNodeTransformMult::PortType VisualShaderNodeTransformMult::get_input_port_type(int p_port) const { +VisualShaderNodeTransformOp::PortType VisualShaderNodeTransformOp::get_input_port_type(int p_port) const { return PORT_TYPE_TRANSFORM; } -String VisualShaderNodeTransformMult::get_input_port_name(int p_port) const { +String VisualShaderNodeTransformOp::get_input_port_name(int p_port) const { return p_port == 0 ? "a" : "b"; } -int VisualShaderNodeTransformMult::get_output_port_count() const { +int VisualShaderNodeTransformOp::get_output_port_count() const { return 1; } -VisualShaderNodeTransformMult::PortType VisualShaderNodeTransformMult::get_output_port_type(int p_port) const { +VisualShaderNodeTransformOp::PortType VisualShaderNodeTransformOp::get_output_port_type(int p_port) const { return PORT_TYPE_TRANSFORM; } -String VisualShaderNodeTransformMult::get_output_port_name(int p_port) const { +String VisualShaderNodeTransformOp::get_output_port_name(int p_port) const { return "mult"; //no output port means the editor will be used as port } -String VisualShaderNodeTransformMult::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 (op == OP_AxB) { - return "\t" + p_output_vars[0] + " = " + p_input_vars[0] + " * " + p_input_vars[1] + ";\n"; - } else if (op == OP_BxA) { - return "\t" + p_output_vars[0] + " = " + p_input_vars[1] + " * " + p_input_vars[0] + ";\n"; - } else if (op == OP_AxB_COMP) { - return "\t" + p_output_vars[0] + " = matrixCompMult(" + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; - } else { - return "\t" + p_output_vars[0] + " = matrixCompMult(" + p_input_vars[1] + ", " + p_input_vars[0] + ");\n"; +String VisualShaderNodeTransformOp::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 { + switch (op) { + case OP_AxB: + return " " + p_output_vars[0] + " = " + p_input_vars[0] + " * " + p_input_vars[1] + ";\n"; + case OP_BxA: + return " " + p_output_vars[0] + " = " + p_input_vars[1] + " * " + p_input_vars[0] + ";\n"; + case OP_AxB_COMP: + return " " + p_output_vars[0] + " = matrixCompMult(" + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; + case OP_BxA_COMP: + return " " + p_output_vars[0] + " = matrixCompMult(" + p_input_vars[1] + ", " + p_input_vars[0] + ");\n"; + case OP_ADD: + return " " + p_output_vars[0] + " = " + p_input_vars[0] + " + " + p_input_vars[1] + ";\n"; + case OP_A_MINUS_B: + return " " + p_output_vars[0] + " = " + p_input_vars[0] + " - " + p_input_vars[1] + ";\n"; + case OP_B_MINUS_A: + return " " + p_output_vars[0] + " = " + p_input_vars[1] + " - " + p_input_vars[0] + ";\n"; + case OP_A_DIV_B: + return " " + p_output_vars[0] + " = " + p_input_vars[0] + " / " + p_input_vars[1] + ";\n"; + case OP_B_DIV_A: + return " " + p_output_vars[0] + " = " + p_input_vars[1] + " / " + p_input_vars[0] + ";\n"; + default: + return ""; } } -void VisualShaderNodeTransformMult::set_operator(Operator p_op) { +void VisualShaderNodeTransformOp::set_operator(Operator p_op) { + ERR_FAIL_INDEX(int(p_op), int(OP_MAX)); + if (op == p_op) { + return; + } op = p_op; emit_changed(); } -VisualShaderNodeTransformMult::Operator VisualShaderNodeTransformMult::get_operator() const { +VisualShaderNodeTransformOp::Operator VisualShaderNodeTransformOp::get_operator() const { return op; } -Vector<StringName> VisualShaderNodeTransformMult::get_editable_properties() const { +Vector<StringName> VisualShaderNodeTransformOp::get_editable_properties() const { Vector<StringName> props; props.push_back("operator"); return props; } -void VisualShaderNodeTransformMult::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_operator", "op"), &VisualShaderNodeTransformMult::set_operator); - ClassDB::bind_method(D_METHOD("get_operator"), &VisualShaderNodeTransformMult::get_operator); +void VisualShaderNodeTransformOp::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_operator", "op"), &VisualShaderNodeTransformOp::set_operator); + ClassDB::bind_method(D_METHOD("get_operator"), &VisualShaderNodeTransformOp::get_operator); - ADD_PROPERTY(PropertyInfo(Variant::INT, "operator", PROPERTY_HINT_ENUM, "A x B,B x A,A x B(per component),B x A(per component)"), "set_operator", "get_operator"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "operator", PROPERTY_HINT_ENUM, "A x B,B x A,A x B(per component),B x A(per component),A + B,A - B,B - A,A / B,B / A"), "set_operator", "get_operator"); BIND_ENUM_CONSTANT(OP_AxB); BIND_ENUM_CONSTANT(OP_BxA); BIND_ENUM_CONSTANT(OP_AxB_COMP); BIND_ENUM_CONSTANT(OP_BxA_COMP); + BIND_ENUM_CONSTANT(OP_ADD); + BIND_ENUM_CONSTANT(OP_A_MINUS_B); + BIND_ENUM_CONSTANT(OP_B_MINUS_A); + BIND_ENUM_CONSTANT(OP_A_DIV_B); + BIND_ENUM_CONSTANT(OP_B_DIV_A); + BIND_ENUM_CONSTANT(OP_MAX); } -VisualShaderNodeTransformMult::VisualShaderNodeTransformMult() { +VisualShaderNodeTransformOp::VisualShaderNodeTransformOp() { set_input_port_default_value(0, Transform3D()); set_input_port_default_value(1, Transform3D()); } @@ -1937,17 +2123,21 @@ String VisualShaderNodeTransformVecMult::get_output_port_name(int p_port) const String VisualShaderNodeTransformVecMult::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 (op == OP_AxB) { - return "\t" + p_output_vars[0] + " = (" + p_input_vars[0] + " * vec4(" + p_input_vars[1] + ", 1.0)).xyz;\n"; + return " " + p_output_vars[0] + " = (" + p_input_vars[0] + " * vec4(" + p_input_vars[1] + ", 1.0)).xyz;\n"; } else if (op == OP_BxA) { - return "\t" + p_output_vars[0] + " = (vec4(" + p_input_vars[1] + ", 1.0) * " + p_input_vars[0] + ").xyz;\n"; + return " " + p_output_vars[0] + " = (vec4(" + p_input_vars[1] + ", 1.0) * " + p_input_vars[0] + ").xyz;\n"; } else if (op == OP_3x3_AxB) { - return "\t" + p_output_vars[0] + " = (" + p_input_vars[0] + " * vec4(" + p_input_vars[1] + ", 0.0)).xyz;\n"; + return " " + p_output_vars[0] + " = (" + p_input_vars[0] + " * vec4(" + p_input_vars[1] + ", 0.0)).xyz;\n"; } else { - return "\t" + p_output_vars[0] + " = (vec4(" + p_input_vars[1] + ", 0.0) * " + p_input_vars[0] + ").xyz;\n"; + return " " + p_output_vars[0] + " = (vec4(" + p_input_vars[1] + ", 0.0) * " + p_input_vars[0] + ").xyz;\n"; } } void VisualShaderNodeTransformVecMult::set_operator(Operator p_op) { + ERR_FAIL_INDEX(int(p_op), int(OP_MAX)); + if (op == p_op) { + return; + } op = p_op; emit_changed(); } @@ -1972,6 +2162,7 @@ void VisualShaderNodeTransformVecMult::_bind_methods() { BIND_ENUM_CONSTANT(OP_BxA); BIND_ENUM_CONSTANT(OP_3x3_AxB); BIND_ENUM_CONSTANT(OP_3x3_BxA); + BIND_ENUM_CONSTANT(OP_MAX); } VisualShaderNodeTransformVecMult::VisualShaderNodeTransformVecMult() { @@ -2010,7 +2201,7 @@ String VisualShaderNodeFloatFunc::get_output_port_name(int p_port) const { } String VisualShaderNodeFloatFunc::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 { - static const char *scalar_func_id[FUNC_ONEMINUS + 1] = { + static const char *functions[FUNC_MAX] = { "sin($)", "cos($)", "tan($)", @@ -2044,11 +2235,14 @@ String VisualShaderNodeFloatFunc::generate_code(Shader::Mode p_mode, VisualShade "trunc($)", "1.0 - $" }; - - return "\t" + p_output_vars[0] + " = " + String(scalar_func_id[func]).replace("$", p_input_vars[0]) + ";\n"; + return " " + p_output_vars[0] + " = " + String(functions[func]).replace("$", p_input_vars[0]) + ";\n"; } void VisualShaderNodeFloatFunc::set_function(Function p_func) { + ERR_FAIL_INDEX(int(p_func), int(FUNC_MAX)); + if (func == p_func) { + return; + } func = p_func; emit_changed(); } @@ -2101,6 +2295,7 @@ void VisualShaderNodeFloatFunc::_bind_methods() { BIND_ENUM_CONSTANT(FUNC_ROUNDEVEN); BIND_ENUM_CONSTANT(FUNC_TRUNC); BIND_ENUM_CONSTANT(FUNC_ONEMINUS); + BIND_ENUM_CONSTANT(FUNC_MAX); } VisualShaderNodeFloatFunc::VisualShaderNodeFloatFunc() { @@ -2138,16 +2333,20 @@ String VisualShaderNodeIntFunc::get_output_port_name(int p_port) const { } String VisualShaderNodeIntFunc::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 { - static const char *int_func_id[FUNC_SIGN + 1] = { + static const char *functions[FUNC_MAX] = { "abs($)", "-($)", "sign($)" }; - return "\t" + p_output_vars[0] + " = " + String(int_func_id[func]).replace("$", p_input_vars[0]) + ";\n"; + return " " + p_output_vars[0] + " = " + String(functions[func]).replace("$", p_input_vars[0]) + ";\n"; } void VisualShaderNodeIntFunc::set_function(Function p_func) { + ERR_FAIL_INDEX(int(p_func), int(FUNC_MAX)); + if (func == p_func) { + return; + } func = p_func; emit_changed(); } @@ -2171,6 +2370,7 @@ void VisualShaderNodeIntFunc::_bind_methods() { BIND_ENUM_CONSTANT(FUNC_ABS); BIND_ENUM_CONSTANT(FUNC_NEGATE); BIND_ENUM_CONSTANT(FUNC_SIGN); + BIND_ENUM_CONSTANT(FUNC_MAX); } VisualShaderNodeIntFunc::VisualShaderNodeIntFunc() { @@ -2208,7 +2408,7 @@ String VisualShaderNodeVectorFunc::get_output_port_name(int p_port) const { } String VisualShaderNodeVectorFunc::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 { - static const char *vec_func_id[FUNC_ONEMINUS + 1] = { + static const char *vec_func_id[FUNC_MAX] = { "normalize($)", "max(min($, vec3(1.0)), vec3(0.0))", "-($)", @@ -2249,39 +2449,43 @@ String VisualShaderNodeVectorFunc::generate_code(Shader::Mode p_mode, VisualShad String code; if (func == FUNC_RGB2HSV) { - code += "\t{\n"; - code += "\t\tvec3 c = " + p_input_vars[0] + ";\n"; - code += "\t\tvec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);\n"; - code += "\t\tvec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));\n"; - code += "\t\tvec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));\n"; - code += "\t\tfloat d = q.x - min(q.w, q.y);\n"; - code += "\t\tfloat e = 1.0e-10;\n"; - code += "\t\t" + p_output_vars[0] + " = vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);\n"; - code += "\t}\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) { - code += "\t{\n"; - code += "\t\tvec3 c = " + p_input_vars[0] + ";\n"; - code += "\t\tvec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);\n"; - code += "\t\tvec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);\n"; - code += "\t\t" + p_output_vars[0] + " = c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);\n"; - code += "\t}\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 += "\t" + p_output_vars[0] + " = " + String(vec_func_id[func]).replace("$", p_input_vars[0]) + ";\n"; + code += " " + p_output_vars[0] + " = " + String(vec_func_id[func]).replace("$", p_input_vars[0]) + ";\n"; } return code; } void VisualShaderNodeVectorFunc::set_function(Function p_func) { - func = p_func; - if (func == FUNC_RGB2HSV) { + ERR_FAIL_INDEX(int(p_func), int(FUNC_MAX)); + if (func == p_func) { + return; + } + if (p_func == FUNC_RGB2HSV) { simple_decl = false; - } else if (func == FUNC_HSV2RGB) { + } else if (p_func == FUNC_HSV2RGB) { simple_decl = false; } else { simple_decl = true; } + func = p_func; emit_changed(); } @@ -2336,6 +2540,7 @@ void VisualShaderNodeVectorFunc::_bind_methods() { BIND_ENUM_CONSTANT(FUNC_TANH); BIND_ENUM_CONSTANT(FUNC_TRUNC); BIND_ENUM_CONSTANT(FUNC_ONEMINUS); + BIND_ENUM_CONSTANT(FUNC_MAX); } VisualShaderNodeVectorFunc::VisualShaderNodeVectorFunc() { @@ -2377,22 +2582,24 @@ String VisualShaderNodeColorFunc::generate_code(Shader::Mode p_mode, VisualShade switch (func) { case FUNC_GRAYSCALE: - code += "\t{\n"; - code += "\t\tvec3 c = " + p_input_vars[0] + ";\n"; - code += "\t\tfloat max1 = max(c.r, c.g);\n"; - code += "\t\tfloat max2 = max(max1, c.b);\n"; - code += "\t\tfloat max3 = max(max1, max2);\n"; - code += "\t\t" + p_output_vars[0] + " = vec3(max3, max3, max3);\n"; - code += "\t}\n"; + code += " {\n"; + code += " vec3 c = " + p_input_vars[0] + ";\n"; + code += " float max1 = max(c.r, c.g);\n"; + code += " float max2 = max(max1, c.b);\n"; + code += " float max3 = max(max1, max2);\n"; + code += " " + p_output_vars[0] + " = vec3(max3, max3, max3);\n"; + code += " }\n"; break; case FUNC_SEPIA: - code += "\t{\n"; - code += "\t\tvec3 c = " + p_input_vars[0] + ";\n"; - code += "\t\tfloat r = (c.r * .393) + (c.g *.769) + (c.b * .189);\n"; - code += "\t\tfloat g = (c.r * .349) + (c.g *.686) + (c.b * .168);\n"; - code += "\t\tfloat b = (c.r * .272) + (c.g *.534) + (c.b * .131);\n"; - code += "\t\t" + p_output_vars[0] + " = vec3(r, g, b);\n"; - code += "\t}\n"; + code += " {\n"; + code += " vec3 c = " + p_input_vars[0] + ";\n"; + code += " float r = (c.r * .393) + (c.g *.769) + (c.b * .189);\n"; + code += " float g = (c.r * .349) + (c.g *.686) + (c.b * .168);\n"; + code += " float b = (c.r * .272) + (c.g *.534) + (c.b * .131);\n"; + code += " " + p_output_vars[0] + " = vec3(r, g, b);\n"; + code += " }\n"; + break; + default: break; } @@ -2400,6 +2607,10 @@ String VisualShaderNodeColorFunc::generate_code(Shader::Mode p_mode, VisualShade } void VisualShaderNodeColorFunc::set_function(Function p_func) { + ERR_FAIL_INDEX(int(p_func), int(FUNC_MAX)); + if (func == p_func) { + return; + } func = p_func; emit_changed(); } @@ -2422,6 +2633,7 @@ void VisualShaderNodeColorFunc::_bind_methods() { BIND_ENUM_CONSTANT(FUNC_GRAYSCALE); BIND_ENUM_CONSTANT(FUNC_SEPIA); + BIND_ENUM_CONSTANT(FUNC_MAX); } VisualShaderNodeColorFunc::VisualShaderNodeColorFunc() { @@ -2460,17 +2672,21 @@ String VisualShaderNodeTransformFunc::get_output_port_name(int p_port) const { } String VisualShaderNodeTransformFunc::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 { - static const char *funcs[FUNC_TRANSPOSE + 1] = { + static const char *functions[FUNC_MAX] = { "inverse($)", "transpose($)" }; String code; - code += "\t" + p_output_vars[0] + " = " + String(funcs[func]).replace("$", p_input_vars[0]) + ";\n"; + code += " " + p_output_vars[0] + " = " + String(functions[func]).replace("$", p_input_vars[0]) + ";\n"; return code; } void VisualShaderNodeTransformFunc::set_function(Function p_func) { + ERR_FAIL_INDEX(int(p_func), int(FUNC_MAX)); + if (func == p_func) { + return; + } func = p_func; emit_changed(); } @@ -2493,6 +2709,7 @@ void VisualShaderNodeTransformFunc::_bind_methods() { BIND_ENUM_CONSTANT(FUNC_INVERSE); BIND_ENUM_CONSTANT(FUNC_TRANSPOSE); + BIND_ENUM_CONSTANT(FUNC_MAX); } VisualShaderNodeTransformFunc::VisualShaderNodeTransformFunc() { @@ -2535,8 +2752,6 @@ String VisualShaderNodeUVFunc::get_input_port_name(int p_port) const { return "offset"; case FUNC_SCALING: return "pivot"; - case FUNC_MAX: - break; default: break; } @@ -2584,29 +2799,28 @@ String VisualShaderNodeUVFunc::generate_code(Shader::Mode p_mode, VisualShader:: switch (func) { case FUNC_PANNING: { - code += vformat("\t%s = fma(%s, %s, %s);\n", p_output_vars[0], offset_pivot, scale, uv); + code += vformat(" %s = fma(%s, %s, %s);\n", p_output_vars[0], offset_pivot, scale, uv); } break; case FUNC_SCALING: { - code += vformat("\t%s = fma((%s - %s), %s, %s);\n", p_output_vars[0], uv, offset_pivot, scale, offset_pivot); + code += vformat(" %s = fma((%s - %s), %s, %s);\n", p_output_vars[0], uv, offset_pivot, scale, offset_pivot); } break; - case FUNC_MAX: + default: break; } return code; } void VisualShaderNodeUVFunc::set_function(VisualShaderNodeUVFunc::Function p_func) { - ERR_FAIL_INDEX(int(p_func), FUNC_MAX); + ERR_FAIL_INDEX(int(p_func), int(FUNC_MAX)); if (func == p_func) { return; } - func = p_func; - if (p_func == FUNC_PANNING) { set_input_port_default_value(2, Vector3()); // offset } else { // FUNC_SCALING set_input_port_default_value(2, Vector3(0.5, 0.5, 0.0)); // pivot } + func = p_func; emit_changed(); } @@ -2667,7 +2881,7 @@ String VisualShaderNodeDotProduct::get_output_port_name(int p_port) const { } String VisualShaderNodeDotProduct::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 "\t" + p_output_vars[0] + " = dot(" + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; + return " " + p_output_vars[0] + " = dot(" + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; } VisualShaderNodeDotProduct::VisualShaderNodeDotProduct() { @@ -2706,7 +2920,7 @@ String VisualShaderNodeVectorLen::get_output_port_name(int p_port) const { } String VisualShaderNodeVectorLen::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 "\t" + p_output_vars[0] + " = length(" + p_input_vars[0] + ");\n"; + return " " + p_output_vars[0] + " = length(" + p_input_vars[0] + ");\n"; } VisualShaderNodeVectorLen::VisualShaderNodeVectorLen() { @@ -2744,7 +2958,7 @@ String VisualShaderNodeDeterminant::get_output_port_name(int p_port) const { } String VisualShaderNodeDeterminant::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 "\t" + p_output_vars[0] + " = determinant(" + p_input_vars[0] + ");\n"; + return " " + p_output_vars[0] + " = determinant(" + p_input_vars[0] + ");\n"; } VisualShaderNodeDeterminant::VisualShaderNodeDeterminant() { @@ -2782,18 +2996,22 @@ String VisualShaderNodeScalarDerivativeFunc::get_output_port_name(int p_port) co } String VisualShaderNodeScalarDerivativeFunc::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 { - static const char *funcs[FUNC_Y + 1] = { + static const char *functions[FUNC_MAX] = { "fwidth($)", "dFdx($)", "dFdy($)" }; String code; - code += "\t" + p_output_vars[0] + " = " + String(funcs[func]).replace("$", p_input_vars[0]) + ";\n"; + code += " " + p_output_vars[0] + " = " + String(functions[func]).replace("$", p_input_vars[0]) + ";\n"; return code; } void VisualShaderNodeScalarDerivativeFunc::set_function(Function p_func) { + ERR_FAIL_INDEX(int(p_func), int(FUNC_MAX)); + if (func == p_func) { + return; + } func = p_func; emit_changed(); } @@ -2817,6 +3035,7 @@ void VisualShaderNodeScalarDerivativeFunc::_bind_methods() { BIND_ENUM_CONSTANT(FUNC_SUM); BIND_ENUM_CONSTANT(FUNC_X); BIND_ENUM_CONSTANT(FUNC_Y); + BIND_ENUM_CONSTANT(FUNC_MAX); } VisualShaderNodeScalarDerivativeFunc::VisualShaderNodeScalarDerivativeFunc() { @@ -2854,18 +3073,22 @@ String VisualShaderNodeVectorDerivativeFunc::get_output_port_name(int p_port) co } String VisualShaderNodeVectorDerivativeFunc::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 { - static const char *funcs[FUNC_Y + 1] = { + static const char *functions[FUNC_MAX] = { "fwidth($)", "dFdx($)", "dFdy($)" }; String code; - code += "\t" + p_output_vars[0] + " = " + String(funcs[func]).replace("$", p_input_vars[0]) + ";\n"; + code += " " + p_output_vars[0] + " = " + String(functions[func]).replace("$", p_input_vars[0]) + ";\n"; return code; } void VisualShaderNodeVectorDerivativeFunc::set_function(Function p_func) { + ERR_FAIL_INDEX(int(p_func), int(FUNC_MAX)); + if (func == p_func) { + return; + } func = p_func; emit_changed(); } @@ -2889,6 +3112,7 @@ void VisualShaderNodeVectorDerivativeFunc::_bind_methods() { BIND_ENUM_CONSTANT(FUNC_SUM); BIND_ENUM_CONSTANT(FUNC_X); BIND_ENUM_CONSTANT(FUNC_Y); + BIND_ENUM_CONSTANT(FUNC_MAX); } VisualShaderNodeVectorDerivativeFunc::VisualShaderNodeVectorDerivativeFunc() { @@ -2949,11 +3173,11 @@ String VisualShaderNodeClamp::get_output_port_name(int p_port) const { } String VisualShaderNodeClamp::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 "\t" + p_output_vars[0] + " = clamp(" + p_input_vars[0] + ", " + p_input_vars[1] + ", " + p_input_vars[2] + ");\n"; + return " " + p_output_vars[0] + " = clamp(" + p_input_vars[0] + ", " + p_input_vars[1] + ", " + p_input_vars[2] + ");\n"; } void VisualShaderNodeClamp::set_op_type(OpType p_op_type) { - ERR_FAIL_INDEX((int)p_op_type, OP_TYPE_MAX); + ERR_FAIL_INDEX((int)p_op_type, int(OP_TYPE_MAX)); if (op_type == p_op_type) { return; } @@ -2991,7 +3215,7 @@ Vector<StringName> VisualShaderNodeClamp::get_editable_properties() const { } void VisualShaderNodeClamp::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_op_type", "type"), &VisualShaderNodeClamp::set_op_type); + 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,Vector"), "set_op_type", "get_op_type"); @@ -3048,7 +3272,7 @@ String VisualShaderNodeFaceForward::get_output_port_name(int p_port) const { } String VisualShaderNodeFaceForward::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 "\t" + p_output_vars[0] + " = faceforward(" + p_input_vars[0] + ", " + p_input_vars[1] + ", " + p_input_vars[2] + ");\n"; + return " " + p_output_vars[0] + " = faceforward(" + p_input_vars[0] + ", " + p_input_vars[1] + ", " + p_input_vars[2] + ");\n"; } VisualShaderNodeFaceForward::VisualShaderNodeFaceForward() { @@ -3095,7 +3319,7 @@ String VisualShaderNodeOuterProduct::get_output_port_name(int p_port) const { } String VisualShaderNodeOuterProduct::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 "\t" + p_output_vars[0] + " = outerProduct(vec4(" + p_input_vars[0] + ", 0.0), vec4(" + p_input_vars[1] + ", 0.0));\n"; + return " " + p_output_vars[0] + " = outerProduct(vec4(" + p_input_vars[0] + ", 0.0), vec4(" + p_input_vars[1] + ", 0.0));\n"; } VisualShaderNodeOuterProduct::VisualShaderNodeOuterProduct() { @@ -3158,7 +3382,7 @@ String VisualShaderNodeStep::get_output_port_name(int p_port) const { } void VisualShaderNodeStep::set_op_type(OpType p_op_type) { - ERR_FAIL_INDEX((int)p_op_type, OP_TYPE_MAX); + ERR_FAIL_INDEX(int(p_op_type), int(OP_TYPE_MAX)); if (op_type == p_op_type) { return; } @@ -3199,7 +3423,7 @@ VisualShaderNodeStep::OpType VisualShaderNodeStep::get_op_type() const { } String VisualShaderNodeStep::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 "\t" + p_output_vars[0] + " = step(" + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; + return " " + p_output_vars[0] + " = step(" + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; } Vector<StringName> VisualShaderNodeStep::get_editable_properties() const { @@ -3209,7 +3433,7 @@ Vector<StringName> VisualShaderNodeStep::get_editable_properties() const { } void VisualShaderNodeStep::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_op_type", "type"), &VisualShaderNodeStep::set_op_type); + 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,Vector,VectorScalar"), "set_op_type", "get_op_type"); @@ -3282,7 +3506,7 @@ String VisualShaderNodeSmoothStep::get_output_port_name(int p_port) const { } void VisualShaderNodeSmoothStep::set_op_type(OpType p_op_type) { - ERR_FAIL_INDEX((int)p_op_type, OP_TYPE_MAX); + ERR_FAIL_INDEX(int(p_op_type), int(OP_TYPE_MAX)); if (op_type == p_op_type) { return; } @@ -3326,7 +3550,7 @@ VisualShaderNodeSmoothStep::OpType VisualShaderNodeSmoothStep::get_op_type() con } String VisualShaderNodeSmoothStep::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 "\t" + p_output_vars[0] + " = smoothstep(" + p_input_vars[0] + ", " + p_input_vars[1] + ", " + p_input_vars[2] + ");\n"; + return " " + p_output_vars[0] + " = smoothstep(" + p_input_vars[0] + ", " + p_input_vars[1] + ", " + p_input_vars[2] + ");\n"; } Vector<StringName> VisualShaderNodeSmoothStep::get_editable_properties() const { @@ -3336,7 +3560,7 @@ Vector<StringName> VisualShaderNodeSmoothStep::get_editable_properties() const { } void VisualShaderNodeSmoothStep::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_op_type", "type"), &VisualShaderNodeSmoothStep::set_op_type); + 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,Vector,VectorScalar"), "set_op_type", "get_op_type"); @@ -3389,7 +3613,7 @@ String VisualShaderNodeVectorDistance::get_output_port_name(int p_port) const { } String VisualShaderNodeVectorDistance::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 "\t" + p_output_vars[0] + " = distance(" + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; + return " " + p_output_vars[0] + " = distance(" + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; } VisualShaderNodeVectorDistance::VisualShaderNodeVectorDistance() { @@ -3439,7 +3663,7 @@ String VisualShaderNodeVectorRefract::get_output_port_name(int p_port) const { } String VisualShaderNodeVectorRefract::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 "\t" + p_output_vars[0] + " = refract(" + p_input_vars[0] + ", " + p_input_vars[1] + ", " + p_input_vars[2] + ");\n"; + return " " + p_output_vars[0] + " = refract(" + p_input_vars[0] + ", " + p_input_vars[1] + ", " + p_input_vars[2] + ");\n"; } VisualShaderNodeVectorRefract::VisualShaderNodeVectorRefract() { @@ -3504,7 +3728,7 @@ String VisualShaderNodeMix::get_output_port_name(int p_port) const { } void VisualShaderNodeMix::set_op_type(OpType p_op_type) { - ERR_FAIL_INDEX((int)p_op_type, OP_TYPE_MAX); + ERR_FAIL_INDEX(int(p_op_type), int(OP_TYPE_MAX)); if (op_type == p_op_type) { return; } @@ -3542,7 +3766,7 @@ VisualShaderNodeMix::OpType VisualShaderNodeMix::get_op_type() const { } String VisualShaderNodeMix::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 "\t" + p_output_vars[0] + " = mix(" + p_input_vars[0] + ", " + p_input_vars[1] + ", " + p_input_vars[2] + ");\n"; + return " " + p_output_vars[0] + " = mix(" + p_input_vars[0] + ", " + p_input_vars[1] + ", " + p_input_vars[2] + ");\n"; } Vector<StringName> VisualShaderNodeMix::get_editable_properties() const { @@ -3552,7 +3776,7 @@ Vector<StringName> VisualShaderNodeMix::get_editable_properties() const { } void VisualShaderNodeMix::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_op_type", "type"), &VisualShaderNodeMix::set_op_type); + 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,Vector,VectorScalar"), "set_op_type", "get_op_type"); @@ -3606,7 +3830,7 @@ String VisualShaderNodeVectorCompose::get_output_port_name(int p_port) const { } String VisualShaderNodeVectorCompose::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 "\t" + p_output_vars[0] + " = vec3(" + p_input_vars[0] + ", " + p_input_vars[1] + ", " + p_input_vars[2] + ");\n"; + return " " + p_output_vars[0] + " = vec3(" + p_input_vars[0] + ", " + p_input_vars[1] + ", " + p_input_vars[2] + ");\n"; } VisualShaderNodeVectorCompose::VisualShaderNodeVectorCompose() { @@ -3654,7 +3878,7 @@ String VisualShaderNodeTransformCompose::get_output_port_name(int p_port) const } String VisualShaderNodeTransformCompose::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 "\t" + p_output_vars[0] + " = mat4(vec4(" + p_input_vars[0] + ", 0.0), vec4(" + p_input_vars[1] + ", 0.0), vec4(" + p_input_vars[2] + ", 0.0), vec4(" + p_input_vars[3] + ", 1.0));\n"; + return " " + p_output_vars[0] + " = mat4(vec4(" + p_input_vars[0] + ", 0.0), vec4(" + p_input_vars[1] + ", 0.0), vec4(" + p_input_vars[2] + ", 0.0), vec4(" + p_input_vars[3] + ", 1.0));\n"; } VisualShaderNodeTransformCompose::VisualShaderNodeTransformCompose() { @@ -3701,9 +3925,9 @@ String VisualShaderNodeVectorDecompose::get_output_port_name(int p_port) const { String VisualShaderNodeVectorDecompose::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 += "\t" + p_output_vars[0] + " = " + p_input_vars[0] + ".x;\n"; - code += "\t" + p_output_vars[1] + " = " + p_input_vars[0] + ".y;\n"; - code += "\t" + p_output_vars[2] + " = " + p_input_vars[0] + ".z;\n"; + 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"; return code; } @@ -3751,10 +3975,10 @@ String VisualShaderNodeTransformDecompose::get_output_port_name(int p_port) cons String VisualShaderNodeTransformDecompose::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 += "\t" + p_output_vars[0] + " = " + p_input_vars[0] + "[0].xyz;\n"; - code += "\t" + p_output_vars[1] + " = " + p_input_vars[0] + "[1].xyz;\n"; - code += "\t" + p_output_vars[2] + " = " + p_input_vars[0] + "[2].xyz;\n"; - code += "\t" + p_output_vars[3] + " = " + p_input_vars[0] + "[3].xyz;\n"; + code += " " + p_output_vars[0] + " = " + p_input_vars[0] + "[0].xyz;\n"; + code += " " + p_output_vars[1] + " = " + p_input_vars[0] + "[1].xyz;\n"; + code += " " + p_output_vars[2] + " = " + p_input_vars[0] + "[2].xyz;\n"; + code += " " + p_output_vars[3] + " = " + p_input_vars[0] + "[3].xyz;\n"; return code; } @@ -3809,7 +4033,7 @@ String VisualShaderNodeFloatUniform::generate_global(Shader::Mode p_mode, Visual } String VisualShaderNodeFloatUniform::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 "\t" + p_output_vars[0] + " = " + get_uniform_name() + ";\n"; + return " " + p_output_vars[0] + " = " + get_uniform_name() + ";\n"; } bool VisualShaderNodeFloatUniform::is_show_prop_names() const { @@ -3821,6 +4045,10 @@ bool VisualShaderNodeFloatUniform::is_use_prop_slots() const { } void VisualShaderNodeFloatUniform::set_hint(Hint p_hint) { + ERR_FAIL_INDEX(int(p_hint), int(HINT_MAX)); + if (hint == p_hint) { + return; + } hint = p_hint; emit_changed(); } @@ -3830,6 +4058,9 @@ VisualShaderNodeFloatUniform::Hint VisualShaderNodeFloatUniform::get_hint() cons } void VisualShaderNodeFloatUniform::set_min(float p_value) { + if (Math::is_equal_approx(hint_range_min, p_value)) { + return; + } hint_range_min = p_value; emit_changed(); } @@ -3839,6 +4070,9 @@ float VisualShaderNodeFloatUniform::get_min() const { } void VisualShaderNodeFloatUniform::set_max(float p_value) { + if (Math::is_equal_approx(hint_range_max, p_value)) { + return; + } hint_range_max = p_value; emit_changed(); } @@ -3848,6 +4082,9 @@ float VisualShaderNodeFloatUniform::get_max() const { } void VisualShaderNodeFloatUniform::set_step(float p_value) { + if (Math::is_equal_approx(hint_range_step, p_value)) { + return; + } hint_range_step = p_value; emit_changed(); } @@ -3857,6 +4094,9 @@ float VisualShaderNodeFloatUniform::get_step() const { } void VisualShaderNodeFloatUniform::set_default_value_enabled(bool p_enabled) { + if (default_value_enabled == p_enabled) { + return; + } default_value_enabled = p_enabled; emit_changed(); } @@ -3866,6 +4106,9 @@ bool VisualShaderNodeFloatUniform::is_default_value_enabled() const { } void VisualShaderNodeFloatUniform::set_default_value(float p_value) { + if (Math::is_equal_approx(default_value, p_value)) { + return; + } default_value = p_value; emit_changed(); } @@ -3903,6 +4146,7 @@ void VisualShaderNodeFloatUniform::_bind_methods() { BIND_ENUM_CONSTANT(HINT_NONE); BIND_ENUM_CONSTANT(HINT_RANGE); BIND_ENUM_CONSTANT(HINT_RANGE_STEP); + BIND_ENUM_CONSTANT(HINT_MAX); } bool VisualShaderNodeFloatUniform::is_qualifier_supported(Qualifier p_qual) const { @@ -3980,7 +4224,7 @@ String VisualShaderNodeIntUniform::generate_global(Shader::Mode p_mode, VisualSh } String VisualShaderNodeIntUniform::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 "\t" + p_output_vars[0] + " = " + get_uniform_name() + ";\n"; + return " " + p_output_vars[0] + " = " + get_uniform_name() + ";\n"; } bool VisualShaderNodeIntUniform::is_show_prop_names() const { @@ -3992,6 +4236,10 @@ bool VisualShaderNodeIntUniform::is_use_prop_slots() const { } void VisualShaderNodeIntUniform::set_hint(Hint p_hint) { + ERR_FAIL_INDEX(int(p_hint), int(HINT_MAX)); + if (hint == p_hint) { + return; + } hint = p_hint; emit_changed(); } @@ -4001,6 +4249,9 @@ VisualShaderNodeIntUniform::Hint VisualShaderNodeIntUniform::get_hint() const { } void VisualShaderNodeIntUniform::set_min(int p_value) { + if (hint_range_min == p_value) { + return; + } hint_range_min = p_value; emit_changed(); } @@ -4010,6 +4261,9 @@ int VisualShaderNodeIntUniform::get_min() const { } void VisualShaderNodeIntUniform::set_max(int p_value) { + if (hint_range_max == p_value) { + return; + } hint_range_max = p_value; emit_changed(); } @@ -4019,6 +4273,9 @@ int VisualShaderNodeIntUniform::get_max() const { } void VisualShaderNodeIntUniform::set_step(int p_value) { + if (hint_range_step == p_value) { + return; + } hint_range_step = p_value; emit_changed(); } @@ -4027,8 +4284,11 @@ int VisualShaderNodeIntUniform::get_step() const { return hint_range_step; } -void VisualShaderNodeIntUniform::set_default_value_enabled(bool p_enabled) { - default_value_enabled = p_enabled; +void VisualShaderNodeIntUniform::set_default_value_enabled(bool p_default_value_enabled) { + if (default_value_enabled == p_default_value_enabled) { + return; + } + default_value_enabled = p_default_value_enabled; emit_changed(); } @@ -4036,8 +4296,11 @@ bool VisualShaderNodeIntUniform::is_default_value_enabled() const { return default_value_enabled; } -void VisualShaderNodeIntUniform::set_default_value(int p_value) { - default_value = p_value; +void VisualShaderNodeIntUniform::set_default_value(int p_default_value) { + if (default_value == p_default_value) { + return; + } + default_value = p_default_value; emit_changed(); } @@ -4074,6 +4337,7 @@ void VisualShaderNodeIntUniform::_bind_methods() { BIND_ENUM_CONSTANT(HINT_NONE); BIND_ENUM_CONSTANT(HINT_RANGE); BIND_ENUM_CONSTANT(HINT_RANGE_STEP); + BIND_ENUM_CONSTANT(HINT_MAX); } bool VisualShaderNodeIntUniform::is_qualifier_supported(Qualifier p_qual) const { @@ -4134,8 +4398,11 @@ String VisualShaderNodeBooleanUniform::get_output_port_name(int p_port) const { return ""; //no output port means the editor will be used as port } -void VisualShaderNodeBooleanUniform::set_default_value_enabled(bool p_enabled) { - default_value_enabled = p_enabled; +void VisualShaderNodeBooleanUniform::set_default_value_enabled(bool p_default_value_enabled) { + if (default_value_enabled == p_default_value_enabled) { + return; + } + default_value_enabled = p_default_value_enabled; emit_changed(); } @@ -4143,8 +4410,11 @@ bool VisualShaderNodeBooleanUniform::is_default_value_enabled() const { return default_value_enabled; } -void VisualShaderNodeBooleanUniform::set_default_value(bool p_value) { - default_value = p_value; +void VisualShaderNodeBooleanUniform::set_default_value(bool p_default_value) { + if (default_value == p_default_value) { + return; + } + default_value = p_default_value; emit_changed(); } @@ -4166,7 +4436,7 @@ String VisualShaderNodeBooleanUniform::generate_global(Shader::Mode p_mode, Visu } String VisualShaderNodeBooleanUniform::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 "\t" + p_output_vars[0] + " = " + get_uniform_name() + ";\n"; + return " " + p_output_vars[0] + " = " + get_uniform_name() + ";\n"; } bool VisualShaderNodeBooleanUniform::is_show_prop_names() const { @@ -4239,6 +4509,9 @@ String VisualShaderNodeColorUniform::get_output_port_name(int p_port) const { } void VisualShaderNodeColorUniform::set_default_value_enabled(bool p_enabled) { + if (default_value_enabled == p_enabled) { + return; + } default_value_enabled = p_enabled; emit_changed(); } @@ -4248,6 +4521,9 @@ bool VisualShaderNodeColorUniform::is_default_value_enabled() const { } void VisualShaderNodeColorUniform::set_default_value(const Color &p_value) { + if (default_value.is_equal_approx(p_value)) { + return; + } default_value = p_value; emit_changed(); } @@ -4266,8 +4542,8 @@ 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 = "\t" + p_output_vars[0] + " = " + get_uniform_name() + ".rgb;\n"; - code += "\t" + p_output_vars[1] + " = " + get_uniform_name() + ".a;\n"; + String code = " " + p_output_vars[0] + " = " + get_uniform_name() + ".rgb;\n"; + code += " " + p_output_vars[1] + " = " + get_uniform_name() + ".a;\n"; return code; } @@ -4364,7 +4640,7 @@ String VisualShaderNodeVec3Uniform::generate_global(Shader::Mode p_mode, VisualS } String VisualShaderNodeVec3Uniform::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 "\t" + p_output_vars[0] + " = " + get_uniform_name() + ";\n"; + return " " + p_output_vars[0] + " = " + get_uniform_name() + ";\n"; } void VisualShaderNodeVec3Uniform::_bind_methods() { @@ -4468,7 +4744,7 @@ String VisualShaderNodeTransformUniform::generate_global(Shader::Mode p_mode, Vi } String VisualShaderNodeTransformUniform::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 "\t" + p_output_vars[0] + " = " + get_uniform_name() + ";\n"; + return " " + p_output_vars[0] + " = " + get_uniform_name() + ";\n"; } void VisualShaderNodeTransformUniform::_bind_methods() { @@ -4491,7 +4767,10 @@ bool VisualShaderNodeTransformUniform::is_use_prop_slots() const { } bool VisualShaderNodeTransformUniform::is_qualifier_supported(Qualifier p_qual) const { - return true; // all qualifiers are supported + if (p_qual == Qualifier::QUAL_INSTANCE) { + return false; + } + return true; } bool VisualShaderNodeTransformUniform::is_convertible_to_constant() const { @@ -4582,6 +4861,9 @@ String VisualShaderNodeTextureUniform::generate_global(Shader::Mode p_mode, Visu case TYPE_ANISO: code += " : hint_aniso;\n"; break; + default: + code += ";\n"; + break; } return code; @@ -4600,28 +4882,32 @@ String VisualShaderNodeTextureUniform::generate_code(Shader::Mode p_mode, Visual } String id = get_uniform_name(); - String code = "\t{\n"; + String code = " {\n"; if (p_input_vars[0] == String()) { // Use UV by default. if (p_input_vars[1] == String()) { - code += "\t\tvec4 n_tex_read = texture(" + id + ", " + default_uv + ");\n"; + code += " vec4 n_tex_read = texture(" + id + ", " + default_uv + ");\n"; } else { - code += "\t\tvec4 n_tex_read = textureLod(" + id + ", " + default_uv + ", " + p_input_vars[1] + ");\n"; + code += " vec4 n_tex_read = textureLod(" + id + ", " + default_uv + ", " + p_input_vars[1] + ");\n"; } } else if (p_input_vars[1] == String()) { //no lod - code += "\t\tvec4 n_tex_read = texture(" + id + ", " + p_input_vars[0] + ".xy);\n"; + code += " vec4 n_tex_read = texture(" + id + ", " + p_input_vars[0] + ".xy);\n"; } else { - code += "\t\tvec4 n_tex_read = textureLod(" + id + ", " + p_input_vars[0] + ".xy, " + p_input_vars[1] + ");\n"; + code += " vec4 n_tex_read = textureLod(" + id + ", " + p_input_vars[0] + ".xy, " + p_input_vars[1] + ");\n"; } - code += "\t\t" + p_output_vars[0] + " = n_tex_read.rgb;\n"; - code += "\t\t" + p_output_vars[1] + " = n_tex_read.a;\n"; - code += "\t}\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; } -void VisualShaderNodeTextureUniform::set_texture_type(TextureType p_type) { - texture_type = p_type; +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) { + return; + } + texture_type = p_texture_type; emit_changed(); } @@ -4629,8 +4915,12 @@ VisualShaderNodeTextureUniform::TextureType VisualShaderNodeTextureUniform::get_ return texture_type; } -void VisualShaderNodeTextureUniform::set_color_default(ColorDefault p_default) { - color_default = p_default; +void VisualShaderNodeTextureUniform::set_color_default(ColorDefault p_color_default) { + ERR_FAIL_INDEX(int(p_color_default), int(COLOR_DEFAULT_MAX)); + if (color_default == p_color_default) { + return; + } + color_default = p_color_default; emit_changed(); } @@ -4659,9 +4949,11 @@ void VisualShaderNodeTextureUniform::_bind_methods() { BIND_ENUM_CONSTANT(TYPE_COLOR); BIND_ENUM_CONSTANT(TYPE_NORMAL_MAP); BIND_ENUM_CONSTANT(TYPE_ANISO); + BIND_ENUM_CONSTANT(TYPE_MAX); BIND_ENUM_CONSTANT(COLOR_DEFAULT_WHITE); BIND_ENUM_CONSTANT(COLOR_DEFAULT_BLACK); + BIND_ENUM_CONSTANT(COLOR_DEFAULT_MAX); } String VisualShaderNodeTextureUniform::get_input_port_default_hint(int p_port) const { @@ -4679,6 +4971,8 @@ bool VisualShaderNodeTextureUniform::is_qualifier_supported(Qualifier p_qual) co return true; case Qualifier::QUAL_INSTANCE: return false; + default: + break; } return false; } @@ -4721,20 +5015,20 @@ String VisualShaderNodeTextureUniformTriplanar::generate_global_per_node(Shader: String code; code += "// TRIPLANAR FUNCTION GLOBAL CODE\n"; - code += "\tvec4 triplanar_texture(sampler2D p_sampler, vec3 p_weights, vec3 p_triplanar_pos) {\n"; - code += "\t\tvec4 samp = vec4(0.0);\n"; - code += "\t\tsamp += texture(p_sampler, p_triplanar_pos.xy) * p_weights.z;\n"; - code += "\t\tsamp += texture(p_sampler, p_triplanar_pos.xz) * p_weights.y;\n"; - code += "\t\tsamp += texture(p_sampler, p_triplanar_pos.zy * vec2(-1.0, 1.0)) * p_weights.x;\n"; - code += "\t\treturn samp;\n"; - code += "\t}\n"; + code += " vec4 triplanar_texture(sampler2D p_sampler, vec3 p_weights, vec3 p_triplanar_pos) {\n"; + code += " vec4 samp = vec4(0.0);\n"; + code += " samp += texture(p_sampler, p_triplanar_pos.xy) * p_weights.z;\n"; + code += " samp += texture(p_sampler, p_triplanar_pos.xz) * p_weights.y;\n"; + code += " samp += texture(p_sampler, p_triplanar_pos.zy * vec2(-1.0, 1.0)) * p_weights.x;\n"; + code += " return samp;\n"; + code += " }\n"; code += "\n"; - code += "\tuniform vec3 triplanar_scale = vec3(1.0, 1.0, 1.0);\n"; - code += "\tuniform vec3 triplanar_offset;\n"; - code += "\tuniform float triplanar_sharpness = 0.5;\n"; + code += " uniform vec3 triplanar_scale = vec3(1.0, 1.0, 1.0);\n"; + code += " uniform vec3 triplanar_offset;\n"; + code += " uniform float triplanar_sharpness = 0.5;\n"; code += "\n"; - code += "\tvarying vec3 triplanar_power_normal;\n"; - code += "\tvarying vec3 triplanar_pos;\n"; + code += " varying vec3 triplanar_power_normal;\n"; + code += " varying vec3 triplanar_pos;\n"; return code; } @@ -4743,11 +5037,11 @@ String VisualShaderNodeTextureUniformTriplanar::generate_global_per_func(Shader: String code; if (p_type == VisualShader::TYPE_VERTEX) { - code += "\t// TRIPLANAR FUNCTION VERTEX CODE\n"; - code += "\t\ttriplanar_power_normal = pow(abs(NORMAL), vec3(triplanar_sharpness));\n"; - code += "\t\ttriplanar_power_normal /= dot(triplanar_power_normal, vec3(1.0));\n"; - code += "\t\ttriplanar_pos = VERTEX * triplanar_scale + triplanar_offset;\n"; - code += "\t\ttriplanar_pos *= vec3(1.0, -1.0, 1.0);\n"; + code += " // TRIPLANAR FUNCTION VERTEX CODE\n"; + code += " triplanar_power_normal = pow(abs(NORMAL), vec3(triplanar_sharpness));\n"; + code += " triplanar_power_normal /= dot(triplanar_power_normal, vec3(1.0));\n"; + code += " triplanar_pos = VERTEX * triplanar_scale + triplanar_offset;\n"; + code += " triplanar_pos *= vec3(1.0, -1.0, 1.0);\n"; } return code; @@ -4755,21 +5049,21 @@ 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 = "\t{\n"; + String code = " {\n"; if (p_input_vars[0] == String() && p_input_vars[1] == String()) { - code += "\t\tvec4 n_tex_read = triplanar_texture(" + id + ", triplanar_power_normal, triplanar_pos);\n"; + code += " vec4 n_tex_read = triplanar_texture(" + id + ", triplanar_power_normal, triplanar_pos);\n"; } else if (p_input_vars[0] != String() && p_input_vars[1] == String()) { - code += "\t\tvec4 n_tex_read = triplanar_texture(" + id + ", " + p_input_vars[0] + ", triplanar_pos);\n"; + code += " vec4 n_tex_read = triplanar_texture(" + id + ", " + p_input_vars[0] + ", triplanar_pos);\n"; } else if (p_input_vars[0] == String() && p_input_vars[1] != String()) { - code += "\t\tvec4 n_tex_read = triplanar_texture(" + id + ", triplanar_power_normal, " + p_input_vars[1] + ");\n"; + code += " vec4 n_tex_read = triplanar_texture(" + id + ", triplanar_power_normal, " + p_input_vars[1] + ");\n"; } else { - code += "\t\tvec4 n_tex_read = triplanar_texture(" + id + ", " + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; + code += " vec4 n_tex_read = triplanar_texture(" + id + ", " + p_input_vars[0] + ", " + p_input_vars[1] + ");\n"; } - code += "\t\t" + p_output_vars[0] + " = n_tex_read.rgb;\n"; - code += "\t\t" + p_output_vars[1] + " = n_tex_read.a;\n"; - code += "\t}\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; } @@ -4844,6 +5138,9 @@ String VisualShaderNodeTexture2DArrayUniform::generate_global(Shader::Mode p_mod case TYPE_ANISO: code += " : hint_aniso;\n"; break; + default: + code += ";\n"; + break; } return code; @@ -4914,6 +5211,9 @@ String VisualShaderNodeTexture3DUniform::generate_global(Shader::Mode p_mode, Vi case TYPE_ANISO: code += " : hint_aniso;\n"; break; + default: + code += ";\n"; + break; } return code; @@ -4984,6 +5284,9 @@ String VisualShaderNodeCubemapUniform::generate_global(Shader::Mode p_mode, Visu case TYPE_ANISO: code += " : hint_aniso;\n"; break; + default: + code += ";\n"; + break; } return code; @@ -5046,18 +5349,18 @@ String VisualShaderNodeIf::get_output_port_name(int p_port) const { String VisualShaderNodeIf::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 += "\tif(abs(" + p_input_vars[0] + " - " + p_input_vars[1] + ") < " + p_input_vars[2] + ")\n"; // abs(a - b) < tolerance eg. a == b - code += "\t{\n"; - code += "\t\t" + p_output_vars[0] + " = " + p_input_vars[3] + ";\n"; - code += "\t}\n"; - code += "\telse if(" + p_input_vars[0] + " < " + p_input_vars[1] + ")\n"; // a < b - code += "\t{\n"; - code += "\t\t" + p_output_vars[0] + " = " + p_input_vars[5] + ";\n"; - code += "\t}\n"; - code += "\telse\n"; // a > b (or a >= b if abs(a - b) < tolerance is false) - code += "\t{\n"; - code += "\t\t" + p_output_vars[0] + " = " + p_input_vars[4] + ";\n"; - code += "\t}\n"; + code += " if(abs(" + p_input_vars[0] + " - " + p_input_vars[1] + ") < " + p_input_vars[2] + ")\n"; // abs(a - b) < tolerance eg. a == b + code += " {\n"; + code += " " + p_output_vars[0] + " = " + p_input_vars[3] + ";\n"; + code += " }\n"; + code += " else if(" + p_input_vars[0] + " < " + p_input_vars[1] + ")\n"; // a < b + code += " {\n"; + code += " " + p_output_vars[0] + " = " + p_input_vars[5] + ";\n"; + code += " }\n"; + code += " else\n"; // a > b (or a >= b if abs(a - b) < tolerance is false) + code += " {\n"; + code += " " + p_output_vars[0] + " = " + p_input_vars[4] + ";\n"; + code += " }\n"; return code; } @@ -5140,7 +5443,7 @@ String VisualShaderNodeSwitch::get_output_port_name(int p_port) const { } void VisualShaderNodeSwitch::set_op_type(OpType p_op_type) { - ERR_FAIL_INDEX((int)p_op_type, OP_TYPE_MAX); + ERR_FAIL_INDEX(int(p_op_type), int(OP_TYPE_MAX)); if (op_type == p_op_type) { return; } @@ -5198,14 +5501,14 @@ void VisualShaderNodeSwitch::_bind_methods() { // static String VisualShaderNodeSwitch::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 += "\tif(" + p_input_vars[0] + ")\n"; - code += "\t{\n"; - code += "\t\t" + p_output_vars[0] + " = " + p_input_vars[1] + ";\n"; - code += "\t}\n"; - code += "\telse\n"; - code += "\t{\n"; - code += "\t\t" + p_output_vars[0] + " = " + p_input_vars[2] + ";\n"; - code += "\t}\n"; + code += " if(" + p_input_vars[0] + ")\n"; + code += " {\n"; + code += " " + p_output_vars[0] + " = " + p_input_vars[1] + ";\n"; + code += " }\n"; + code += " else\n"; + code += " {\n"; + code += " " + p_output_vars[0] + " = " + p_input_vars[2] + ";\n"; + code += " }\n"; return code; } @@ -5290,12 +5593,12 @@ String VisualShaderNodeFresnel::generate_code(Shader::Mode p_mode, VisualShader: } if (is_input_port_connected(2)) { - return "\t" + p_output_vars[0] + " = " + p_input_vars[2] + " ? (pow(clamp(dot(" + normal + ", " + view + "), 0.0, 1.0), " + p_input_vars[3] + ")) : (pow(1.0 - clamp(dot(" + normal + ", " + view + "), 0.0, 1.0), " + p_input_vars[3] + "));\n"; + return " " + p_output_vars[0] + " = " + p_input_vars[2] + " ? (pow(clamp(dot(" + normal + ", " + view + "), 0.0, 1.0), " + p_input_vars[3] + ")) : (pow(1.0 - clamp(dot(" + normal + ", " + view + "), 0.0, 1.0), " + p_input_vars[3] + "));\n"; } else { if (get_input_port_default_value(2)) { - return "\t" + p_output_vars[0] + " = pow(clamp(dot(" + normal + ", " + view + "), 0.0, 1.0), " + p_input_vars[3] + ");\n"; + return " " + p_output_vars[0] + " = pow(clamp(dot(" + normal + ", " + view + "), 0.0, 1.0), " + p_input_vars[3] + ");\n"; } else { - return "\t" + p_output_vars[0] + " = pow(1.0 - clamp(dot(" + normal + ", " + view + "), 0.0, 1.0), " + p_input_vars[3] + ");\n"; + return " " + p_output_vars[0] + " = pow(1.0 - clamp(dot(" + normal + ", " + view + "), 0.0, 1.0), " + p_input_vars[3] + ");\n"; } } } @@ -5345,17 +5648,21 @@ String VisualShaderNodeIs::get_output_port_name(int p_port) const { } String VisualShaderNodeIs::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 { - static const char *funcs[FUNC_IS_NAN + 1] = { + static const char *functions[FUNC_MAX] = { "isinf($)", "isnan($)" }; String code; - code += "\t" + p_output_vars[0] + " = " + String(funcs[func]).replace("$", p_input_vars[0]) + ";\n"; + code += " " + p_output_vars[0] + " = " + String(functions[func]).replace("$", p_input_vars[0]) + ";\n"; return code; } void VisualShaderNodeIs::set_function(Function p_func) { + ERR_FAIL_INDEX(int(p_func), int(FUNC_MAX)); + if (func == p_func) { + return; + } func = p_func; emit_changed(); } @@ -5378,6 +5685,7 @@ void VisualShaderNodeIs::_bind_methods() { BIND_ENUM_CONSTANT(FUNC_IS_INF); BIND_ENUM_CONSTANT(FUNC_IS_NAN); + BIND_ENUM_CONSTANT(FUNC_MAX); } VisualShaderNodeIs::VisualShaderNodeIs() { @@ -5391,14 +5699,14 @@ String VisualShaderNodeCompare::get_caption() const { } int VisualShaderNodeCompare::get_input_port_count() const { - if (ctype == CTYPE_SCALAR && (func == FUNC_EQUAL || func == FUNC_NOT_EQUAL)) { + if (comparison_type == CTYPE_SCALAR && (func == FUNC_EQUAL || func == FUNC_NOT_EQUAL)) { return 3; } return 2; } VisualShaderNodeCompare::PortType VisualShaderNodeCompare::get_input_port_type(int p_port) const { - switch (ctype) { + switch (comparison_type) { case CTYPE_SCALAR: return PORT_TYPE_SCALAR; case CTYPE_SCALAR_INT: @@ -5441,17 +5749,16 @@ String VisualShaderNodeCompare::get_output_port_name(int p_port) const { } String VisualShaderNodeCompare::get_warning(Shader::Mode p_mode, VisualShader::Type p_type) const { - if (ctype == CTYPE_BOOLEAN || ctype == CTYPE_TRANSFORM) { + if (comparison_type == CTYPE_BOOLEAN || comparison_type == CTYPE_TRANSFORM) { if (func > FUNC_NOT_EQUAL) { return TTR("Invalid comparison function for that type."); } } - return ""; } String VisualShaderNodeCompare::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 { - static const char *ops[FUNC_LESS_THAN_EQUAL + 1] = { + static const char *operators[FUNC_MAX] = { "==", "!=", ">", @@ -5460,7 +5767,7 @@ String VisualShaderNodeCompare::generate_code(Shader::Mode p_mode, VisualShader: "<=", }; - static const char *funcs[FUNC_LESS_THAN_EQUAL + 1] = { + static const char *functions[FUNC_MAX] = { "equal($)", "notEqual($)", "greaterThan($)", @@ -5469,46 +5776,46 @@ String VisualShaderNodeCompare::generate_code(Shader::Mode p_mode, VisualShader: "lessThanEqual($)", }; - static const char *conds[COND_ANY + 1] = { + static const char *conditions[COND_MAX] = { "all($)", "any($)", }; String code; - switch (ctype) { + switch (comparison_type) { case CTYPE_SCALAR: if (func == FUNC_EQUAL) { - code += "\t" + p_output_vars[0] + " = (abs(" + p_input_vars[0] + " - " + p_input_vars[1] + ") < " + p_input_vars[2] + ");"; + code += " " + p_output_vars[0] + " = (abs(" + p_input_vars[0] + " - " + p_input_vars[1] + ") < " + p_input_vars[2] + ");"; } else if (func == FUNC_NOT_EQUAL) { - code += "\t" + p_output_vars[0] + " = !(abs(" + p_input_vars[0] + " - " + p_input_vars[1] + ") < " + p_input_vars[2] + ");"; + code += " " + p_output_vars[0] + " = !(abs(" + p_input_vars[0] + " - " + p_input_vars[1] + ") < " + p_input_vars[2] + ");"; } else { - code += "\t" + p_output_vars[0] + " = " + (p_input_vars[0] + " $ " + p_input_vars[1]).replace("$", ops[func]) + ";\n"; + code += " " + p_output_vars[0] + " = " + (p_input_vars[0] + " $ " + p_input_vars[1]).replace("$", operators[func]) + ";\n"; } break; case CTYPE_SCALAR_INT: - code += "\t" + p_output_vars[0] + " = " + (p_input_vars[0] + " $ " + p_input_vars[1]).replace("$", ops[func]) + ";\n"; + code += " " + p_output_vars[0] + " = " + (p_input_vars[0] + " $ " + p_input_vars[1]).replace("$", operators[func]) + ";\n"; break; case CTYPE_VECTOR: - code += "\t{\n"; - code += "\t\tbvec3 _bv = " + String(funcs[func]).replace("$", p_input_vars[0] + ", " + p_input_vars[1]) + ";\n"; - code += "\t\t" + p_output_vars[0] + " = " + String(conds[condition]).replace("$", "_bv") + ";\n"; - code += "\t}\n"; + code += " {\n"; + code += " bvec3 _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 "\t" + p_output_vars[0] + " = false;\n"; + return " " + p_output_vars[0] + " = false;\n"; } - code += "\t" + p_output_vars[0] + " = " + (p_input_vars[0] + " $ " + p_input_vars[1]).replace("$", ops[func]) + ";\n"; + code += " " + p_output_vars[0] + " = " + (p_input_vars[0] + " $ " + p_input_vars[1]).replace("$", operators[func]) + ";\n"; break; case CTYPE_TRANSFORM: if (func > FUNC_NOT_EQUAL) { - return "\t" + p_output_vars[0] + " = false;\n"; + return " " + p_output_vars[0] + " = false;\n"; } - code += "\t" + p_output_vars[0] + " = " + (p_input_vars[0] + " $ " + p_input_vars[1]).replace("$", ops[func]) + ";\n"; + code += " " + p_output_vars[0] + " = " + (p_input_vars[0] + " $ " + p_input_vars[1]).replace("$", operators[func]) + ";\n"; break; default: @@ -5517,10 +5824,12 @@ String VisualShaderNodeCompare::generate_code(Shader::Mode p_mode, VisualShader: return code; } -void VisualShaderNodeCompare::set_comparison_type(ComparisonType p_type) { - ctype = p_type; - - switch (ctype) { +void VisualShaderNodeCompare::set_comparison_type(ComparisonType p_comparison_type) { + ERR_FAIL_INDEX(int(p_comparison_type), int(CTYPE_MAX)); + if (comparison_type == p_comparison_type) { + return; + } + switch (p_comparison_type) { case CTYPE_SCALAR: set_input_port_default_value(0, 0.0); set_input_port_default_value(1, 0.0); @@ -5546,15 +5855,22 @@ void VisualShaderNodeCompare::set_comparison_type(ComparisonType p_type) { set_input_port_default_value(1, Transform3D()); simple_decl = true; break; + default: + break; } + comparison_type = p_comparison_type; emit_changed(); } VisualShaderNodeCompare::ComparisonType VisualShaderNodeCompare::get_comparison_type() const { - return ctype; + return comparison_type; } void VisualShaderNodeCompare::set_function(Function p_func) { + ERR_FAIL_INDEX(int(p_func), int(FUNC_MAX)); + if (func == p_func) { + return; + } func = p_func; emit_changed(); } @@ -5563,8 +5879,12 @@ VisualShaderNodeCompare::Function VisualShaderNodeCompare::get_function() const return func; } -void VisualShaderNodeCompare::set_condition(Condition p_cond) { - condition = p_cond; +void VisualShaderNodeCompare::set_condition(Condition p_condition) { + ERR_FAIL_INDEX(int(p_condition), int(COND_MAX)); + if (condition == p_condition) { + return; + } + condition = p_condition; emit_changed(); } @@ -5576,7 +5896,7 @@ Vector<StringName> VisualShaderNodeCompare::get_editable_properties() const { Vector<StringName> props; props.push_back("type"); props.push_back("function"); - if (ctype == CTYPE_VECTOR) { + if (comparison_type == CTYPE_VECTOR) { props.push_back("condition"); } return props; @@ -5601,6 +5921,7 @@ void VisualShaderNodeCompare::_bind_methods() { BIND_ENUM_CONSTANT(CTYPE_VECTOR); BIND_ENUM_CONSTANT(CTYPE_BOOLEAN); BIND_ENUM_CONSTANT(CTYPE_TRANSFORM); + BIND_ENUM_CONSTANT(CTYPE_MAX); BIND_ENUM_CONSTANT(FUNC_EQUAL); BIND_ENUM_CONSTANT(FUNC_NOT_EQUAL); @@ -5608,9 +5929,11 @@ void VisualShaderNodeCompare::_bind_methods() { BIND_ENUM_CONSTANT(FUNC_GREATER_THAN_EQUAL); BIND_ENUM_CONSTANT(FUNC_LESS_THAN); BIND_ENUM_CONSTANT(FUNC_LESS_THAN_EQUAL); + BIND_ENUM_CONSTANT(FUNC_MAX); BIND_ENUM_CONSTANT(COND_ALL); BIND_ENUM_CONSTANT(COND_ANY); + BIND_ENUM_CONSTANT(COND_MAX); } VisualShaderNodeCompare::VisualShaderNodeCompare() { @@ -5664,11 +5987,11 @@ String VisualShaderNodeMultiplyAdd::get_output_port_name(int p_port) const { } String VisualShaderNodeMultiplyAdd::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 "\t" + p_output_vars[0] + " = fma(" + p_input_vars[0] + ", " + p_input_vars[1] + ", " + p_input_vars[2] + ");\n"; + return " " + p_output_vars[0] + " = fma(" + p_input_vars[0] + ", " + p_input_vars[1] + ", " + p_input_vars[2] + ");\n"; } void VisualShaderNodeMultiplyAdd::set_op_type(OpType p_op_type) { - ERR_FAIL_INDEX((int)p_op_type, OP_TYPE_MAX); + ERR_FAIL_INDEX((int)p_op_type, int(OP_TYPE_MAX)); if (op_type == p_op_type) { return; } @@ -5752,34 +6075,34 @@ String VisualShaderNodeBillboard::generate_code(Shader::Mode p_mode, VisualShade switch (billboard_type) { case BILLBOARD_TYPE_ENABLED: - code += "\t{\n"; - code += "\t\tmat4 __mvm = INV_CAMERA_MATRIX * mat4(CAMERA_MATRIX[0], CAMERA_MATRIX[1], CAMERA_MATRIX[2], WORLD_MATRIX[3]);\n"; + code += " {\n"; + code += " mat4 __mvm = INV_CAMERA_MATRIX * mat4(CAMERA_MATRIX[0], CAMERA_MATRIX[1], CAMERA_MATRIX[2], WORLD_MATRIX[3]);\n"; if (keep_scale) { - code += "\t\t__mvm = __mvm * mat4(vec4(length(WORLD_MATRIX[0].xyz), 0.0, 0.0, 0.0), vec4(0.0, length(WORLD_MATRIX[1].xyz), 0.0, 0.0), vec4(0.0, 0.0, length(WORLD_MATRIX[2].xyz), 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; + code += " __mvm = __mvm * mat4(vec4(length(WORLD_MATRIX[0].xyz), 0.0, 0.0, 0.0), vec4(0.0, length(WORLD_MATRIX[1].xyz), 0.0, 0.0), vec4(0.0, 0.0, length(WORLD_MATRIX[2].xyz), 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; } - code += "\t\t" + p_output_vars[0] + " = __mvm;\n"; - code += "\t}\n"; + code += " " + p_output_vars[0] + " = __mvm;\n"; + code += " }\n"; break; case BILLBOARD_TYPE_FIXED_Y: - code += "\t{\n"; - code += "\t\tmat4 __mvm = INV_CAMERA_MATRIX * mat4(CAMERA_MATRIX[0], WORLD_MATRIX[1], vec4(normalize(cross(CAMERA_MATRIX[0].xyz, WORLD_MATRIX[1].xyz)), 0.0), WORLD_MATRIX[3]);\n"; + code += " {\n"; + code += " mat4 __mvm = INV_CAMERA_MATRIX * mat4(CAMERA_MATRIX[0], WORLD_MATRIX[1], vec4(normalize(cross(CAMERA_MATRIX[0].xyz, WORLD_MATRIX[1].xyz)), 0.0), WORLD_MATRIX[3]);\n"; if (keep_scale) { - code += "\t\t__mvm = __mvm * mat4(vec4(length(WORLD_MATRIX[0].xyz), 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, length(WORLD_MATRIX[2].xyz), 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; + code += " __mvm = __mvm * mat4(vec4(length(WORLD_MATRIX[0].xyz), 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, length(WORLD_MATRIX[2].xyz), 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; } else { - code += "\t\t__mvm = __mvm * mat4(vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0 / length(WORLD_MATRIX[1].xyz), 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; + code += " __mvm = __mvm * mat4(vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0 / length(WORLD_MATRIX[1].xyz), 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; } - code += "\t\t" + p_output_vars[0] + " = __mvm;\n"; - code += "\t}\n"; + code += " " + p_output_vars[0] + " = __mvm;\n"; + code += " }\n"; break; case BILLBOARD_TYPE_PARTICLES: - code += "\t{\n"; - code += "\t\tmat4 __wm = mat4(normalize(CAMERA_MATRIX[0]) * length(WORLD_MATRIX[0]), normalize(CAMERA_MATRIX[1]) * length(WORLD_MATRIX[0]), normalize(CAMERA_MATRIX[2]) * length(WORLD_MATRIX[2]), WORLD_MATRIX[3]);\n"; - code += "\t\t__wm = __wm * mat4(vec4(cos(INSTANCE_CUSTOM.x), -sin(INSTANCE_CUSTOM.x), 0.0, 0.0), vec4(sin(INSTANCE_CUSTOM.x), cos(INSTANCE_CUSTOM.x), 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; - code += "\t\t" + p_output_vars[0] + " = INV_CAMERA_MATRIX * __wm;\n"; - code += "\t}\n"; + code += " {\n"; + code += " mat4 __wm = mat4(normalize(CAMERA_MATRIX[0]) * length(WORLD_MATRIX[0]), normalize(CAMERA_MATRIX[1]) * length(WORLD_MATRIX[0]), normalize(CAMERA_MATRIX[2]) * length(WORLD_MATRIX[2]), WORLD_MATRIX[3]);\n"; + code += " __wm = __wm * mat4(vec4(cos(INSTANCE_CUSTOM.x), -sin(INSTANCE_CUSTOM.x), 0.0, 0.0), vec4(sin(INSTANCE_CUSTOM.x), cos(INSTANCE_CUSTOM.x), 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; + code += " " + p_output_vars[0] + " = INV_CAMERA_MATRIX * __wm;\n"; + code += " }\n"; break; default: - code += "\t" + p_output_vars[0] + " = mat4(1.0);\n"; + code += " " + p_output_vars[0] + " = mat4(1.0);\n"; break; } @@ -5791,7 +6114,10 @@ bool VisualShaderNodeBillboard::is_show_prop_names() const { } void VisualShaderNodeBillboard::set_billboard_type(BillboardType p_billboard_type) { - ERR_FAIL_INDEX((int)p_billboard_type, BILLBOARD_TYPE_MAX); + ERR_FAIL_INDEX(int(p_billboard_type), int(BILLBOARD_TYPE_MAX)); + if (billboard_type == p_billboard_type) { + return; + } billboard_type = p_billboard_type; simple_decl = bool(billboard_type == BILLBOARD_TYPE_DISABLED); set_disabled(simple_decl); diff --git a/scene/resources/visual_shader_nodes.h b/scene/resources/visual_shader_nodes.h index 5b44e9f776..2c952300fe 100644 --- a/scene/resources/visual_shader_nodes.h +++ b/scene/resources/visual_shader_nodes.h @@ -76,7 +76,7 @@ public: 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(float p_value); + void set_constant(float p_constant); float get_constant() const; virtual Vector<StringName> get_editable_properties() const override; @@ -106,7 +106,7 @@ public: 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(int p_value); + void set_constant(int p_constant); int get_constant() const; virtual Vector<StringName> get_editable_properties() const override; @@ -136,7 +136,7 @@ public: 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(bool p_value); + void set_constant(bool p_constant); bool get_constant() const; virtual Vector<StringName> get_editable_properties() const override; @@ -167,7 +167,7 @@ public: 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(Color p_value); + void set_constant(const Color &p_constant); Color get_constant() const; virtual Vector<StringName> get_editable_properties() const override; @@ -197,7 +197,7 @@ public: 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(Vector3 p_value); + void set_constant(const Vector3 &p_constant); Vector3 get_constant() const; virtual Vector<StringName> get_editable_properties() const override; @@ -227,7 +227,7 @@ public: 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(Transform3D p_value); + void set_constant(const Transform3D &p_constant); Transform3D get_constant() const; virtual Vector<StringName> get_editable_properties() const override; @@ -251,12 +251,14 @@ public: SOURCE_2D_NORMAL, SOURCE_DEPTH, SOURCE_PORT, + SOURCE_MAX, }; enum TextureType { TYPE_DATA, TYPE_COLOR, TYPE_NORMAL_MAP, + TYPE_MAX, }; private: @@ -287,10 +289,10 @@ public: void set_source(Source p_source); Source get_source() const; - void set_texture(Ref<Texture2D> p_value); + void set_texture(Ref<Texture2D> p_texture); Ref<Texture2D> get_texture() const; - void set_texture_type(TextureType p_type); + void set_texture_type(TextureType p_texture_type); TextureType get_texture_type() const; virtual Vector<StringName> get_editable_properties() const override; @@ -327,7 +329,7 @@ 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; - void set_texture(Ref<CurveTexture> p_value); + void set_texture(Ref<CurveTexture> p_texture); Ref<CurveTexture> get_texture() const; virtual Vector<StringName> get_editable_properties() const override; @@ -338,6 +340,39 @@ public: /////////////////////////////////////// +class VisualShaderNodeCurveXYZTexture : public VisualShaderNodeResizableBase { + GDCLASS(VisualShaderNodeCurveXYZTexture, VisualShaderNodeResizableBase); + Ref<CurveXYZTexture> texture; + +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 Vector<VisualShader::DefaultTextureParam> get_default_texture_parameters(VisualShader::Type p_type, int p_id) const override; + virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; + + void set_texture(Ref<CurveXYZTexture> p_texture); + Ref<CurveXYZTexture> get_texture() const; + + virtual Vector<StringName> get_editable_properties() const override; + virtual bool is_use_prop_slots() const override; + + VisualShaderNodeCurveXYZTexture(); +}; + +/////////////////////////////////////// + class VisualShaderNodeSample3D : public VisualShaderNode { GDCLASS(VisualShaderNodeSample3D, VisualShaderNode); @@ -345,6 +380,7 @@ public: enum Source { SOURCE_TEXTURE, SOURCE_PORT, + SOURCE_MAX, }; protected: @@ -377,7 +413,7 @@ VARIANT_ENUM_CAST(VisualShaderNodeSample3D::Source) class VisualShaderNodeTexture2DArray : public VisualShaderNodeSample3D { GDCLASS(VisualShaderNodeTexture2DArray, VisualShaderNodeSample3D); - Ref<Texture2DArray> texture; + Ref<Texture2DArray> texture_array; protected: static void _bind_methods(); @@ -390,7 +426,7 @@ public: virtual Vector<VisualShader::DefaultTextureParam> get_default_texture_parameters(VisualShader::Type p_type, int p_id) const override; virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; - void set_texture_array(Ref<Texture2DArray> p_value); + void set_texture_array(Ref<Texture2DArray> p_texture_array); Ref<Texture2DArray> get_texture_array() const; virtual Vector<StringName> get_editable_properties() const override; @@ -413,7 +449,7 @@ public: virtual Vector<VisualShader::DefaultTextureParam> get_default_texture_parameters(VisualShader::Type p_type, int p_id) const override; virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; - void set_texture(Ref<Texture3D> p_value); + void set_texture(Ref<Texture3D> p_texture); Ref<Texture3D> get_texture() const; virtual Vector<StringName> get_editable_properties() const override; @@ -428,13 +464,15 @@ class VisualShaderNodeCubemap : public VisualShaderNode { public: enum Source { SOURCE_TEXTURE, - SOURCE_PORT + SOURCE_PORT, + SOURCE_MAX, }; enum TextureType { TYPE_DATA, TYPE_COLOR, - TYPE_NORMAL_MAP + TYPE_NORMAL_MAP, + TYPE_MAX, }; private: @@ -464,10 +502,10 @@ public: void set_source(Source p_source); Source get_source() const; - void set_cube_map(Ref<Cubemap> p_value); + void set_cube_map(Ref<Cubemap> p_cube_map); Ref<Cubemap> get_cube_map() const; - void set_texture_type(TextureType p_type); + void set_texture_type(TextureType p_texture_type); TextureType get_texture_type() const; virtual Vector<StringName> get_editable_properties() const override; @@ -497,7 +535,8 @@ public: OP_MAX, OP_MIN, OP_ATAN2, - OP_STEP + OP_STEP, + OP_ENUM_SIZE, }; protected: @@ -540,6 +579,7 @@ public: OP_MOD, OP_MAX, OP_MIN, + OP_ENUM_SIZE, }; protected: @@ -586,7 +626,8 @@ public: OP_CROSS, OP_ATAN2, OP_REFLECT, - OP_STEP + OP_STEP, + OP_ENUM_SIZE, }; protected: @@ -632,7 +673,8 @@ public: OP_DODGE, OP_BURN, OP_SOFT_LIGHT, - OP_HARD_LIGHT + OP_HARD_LIGHT, + OP_MAX, }; protected: @@ -663,19 +705,25 @@ public: VARIANT_ENUM_CAST(VisualShaderNodeColorOp::Operator) -/////////////////////////////////////// -/// TRANSFORM-TRANSFORM MULTIPLICATION -/////////////////////////////////////// +//////////////////////////////// +/// TRANSFORM-TRANSFORM OPERATOR +//////////////////////////////// -class VisualShaderNodeTransformMult : public VisualShaderNode { - GDCLASS(VisualShaderNodeTransformMult, VisualShaderNode); +class VisualShaderNodeTransformOp : public VisualShaderNode { + GDCLASS(VisualShaderNodeTransformOp, VisualShaderNode); public: enum Operator { OP_AxB, OP_BxA, OP_AxB_COMP, - OP_BxA_COMP + OP_BxA_COMP, + OP_ADD, + OP_A_MINUS_B, + OP_B_MINUS_A, + OP_A_DIV_B, + OP_B_DIV_A, + OP_MAX, }; protected: @@ -701,10 +749,10 @@ public: virtual Vector<StringName> get_editable_properties() const override; - VisualShaderNodeTransformMult(); + VisualShaderNodeTransformOp(); }; -VARIANT_ENUM_CAST(VisualShaderNodeTransformMult::Operator) +VARIANT_ENUM_CAST(VisualShaderNodeTransformOp::Operator) /////////////////////////////////////// /// TRANSFORM-VECTOR MULTIPLICATION @@ -719,6 +767,7 @@ public: OP_BxA, OP_3x3_AxB, OP_3x3_BxA, + OP_MAX, }; protected: @@ -789,7 +838,8 @@ public: FUNC_RECIPROCAL, FUNC_ROUNDEVEN, FUNC_TRUNC, - FUNC_ONEMINUS + FUNC_ONEMINUS, + FUNC_MAX, }; protected: @@ -832,6 +882,7 @@ public: FUNC_ABS, FUNC_NEGATE, FUNC_SIGN, + FUNC_MAX, }; protected: @@ -905,7 +956,8 @@ public: FUNC_TAN, FUNC_TANH, FUNC_TRUNC, - FUNC_ONEMINUS + FUNC_ONEMINUS, + FUNC_MAX, }; protected: @@ -946,7 +998,8 @@ class VisualShaderNodeColorFunc : public VisualShaderNode { public: enum Function { FUNC_GRAYSCALE, - FUNC_SEPIA + FUNC_SEPIA, + FUNC_MAX, }; protected: @@ -987,7 +1040,8 @@ class VisualShaderNodeTransformFunc : public VisualShaderNode { public: enum Function { FUNC_INVERSE, - FUNC_TRANSPOSE + FUNC_TRANSPOSE, + FUNC_MAX, }; protected: @@ -1053,7 +1107,7 @@ public: 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_function(Function p_op); + void set_function(Function p_func); Function get_function() const; virtual Vector<StringName> get_editable_properties() const override; @@ -1162,7 +1216,7 @@ 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_op_type(OpType p_type); + void set_op_type(OpType p_op_type); OpType get_op_type() const; virtual Vector<StringName> get_editable_properties() const override; @@ -1185,7 +1239,8 @@ public: enum Function { FUNC_SUM, FUNC_X, - FUNC_Y + FUNC_Y, + FUNC_MAX, }; protected: @@ -1225,7 +1280,8 @@ public: enum Function { FUNC_SUM, FUNC_X, - FUNC_Y + FUNC_Y, + FUNC_MAX, }; protected: @@ -1332,7 +1388,7 @@ 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_op_type(OpType p_type); + void set_op_type(OpType p_op_type); OpType get_op_type() const; virtual Vector<StringName> get_editable_properties() const override; @@ -1374,7 +1430,7 @@ 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_op_type(OpType p_type); + void set_op_type(OpType p_op_type); OpType get_op_type() const; virtual Vector<StringName> get_editable_properties() const override; @@ -1462,7 +1518,7 @@ 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_op_type(OpType p_type); + void set_op_type(OpType p_op_type); OpType get_op_type() const; virtual Vector<StringName> get_editable_properties() const override; @@ -1574,6 +1630,7 @@ public: HINT_NONE, HINT_RANGE, HINT_RANGE_STEP, + HINT_MAX, }; private: @@ -1640,6 +1697,7 @@ public: HINT_NONE, HINT_RANGE, HINT_RANGE_STEP, + HINT_MAX, }; private: @@ -1880,11 +1938,13 @@ public: TYPE_COLOR, TYPE_NORMAL_MAP, TYPE_ANISO, + TYPE_MAX, }; enum ColorDefault { COLOR_DEFAULT_WHITE, - COLOR_DEFAULT_BLACK + COLOR_DEFAULT_BLACK, + COLOR_DEFAULT_MAX, }; protected: @@ -2074,7 +2134,7 @@ 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_op_type(OpType p_type); + void set_op_type(OpType p_op_type); OpType get_op_type() const; virtual Vector<StringName> get_editable_properties() const override; @@ -2122,6 +2182,7 @@ public: enum Function { FUNC_IS_INF, FUNC_IS_NAN, + FUNC_MAX, }; protected: @@ -2167,6 +2228,7 @@ public: CTYPE_VECTOR, CTYPE_BOOLEAN, CTYPE_TRANSFORM, + CTYPE_MAX, }; enum Function { @@ -2176,15 +2238,17 @@ public: FUNC_GREATER_THAN_EQUAL, FUNC_LESS_THAN, FUNC_LESS_THAN_EQUAL, + FUNC_MAX, }; enum Condition { COND_ALL, COND_ANY, + COND_MAX, }; protected: - ComparisonType ctype = CTYPE_SCALAR; + ComparisonType comparison_type = CTYPE_SCALAR; Function func = FUNC_EQUAL; Condition condition = COND_ALL; @@ -2252,7 +2316,7 @@ public: 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_op_type(OpType p_type); + void set_op_type(OpType p_op_type); OpType get_op_type() const; virtual Vector<StringName> get_editable_properties() const override; diff --git a/scene/resources/visual_shader_particle_nodes.cpp b/scene/resources/visual_shader_particle_nodes.cpp index 29d583a82a..5fe801e037 100644 --- a/scene/resources/visual_shader_particle_nodes.cpp +++ b/scene/resources/visual_shader_particle_nodes.cpp @@ -76,14 +76,14 @@ String VisualShaderNodeParticleSphereEmitter::get_input_port_name(int p_port) co String VisualShaderNodeParticleSphereEmitter::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { String code; code += "vec3 __get_random_point_in_sphere(inout uint seed, float radius, float inner_radius) {\n"; - code += "\treturn __get_random_unit_vec3(seed) * __randf_range(seed, inner_radius, radius);\n"; + code += " return __get_random_unit_vec3(seed) * __randf_range(seed, inner_radius, radius);\n"; code += "}\n\n"; return code; } String VisualShaderNodeParticleSphereEmitter::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { String code; - code += "\t" + p_output_vars[0] + " = __get_random_point_in_sphere(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ", " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ");\n"; + code += " " + p_output_vars[0] + " = __get_random_point_in_sphere(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ", " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ");\n"; return code; } @@ -119,15 +119,15 @@ String VisualShaderNodeParticleBoxEmitter::get_input_port_name(int p_port) const String VisualShaderNodeParticleBoxEmitter::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { String code; code += "vec3 __get_random_point_in_box(inout uint seed, vec3 extents) {\n"; - code += "\tvec3 half_extents = extents / 2.0;\n"; - code += "\treturn vec3(__randf_range(seed, -half_extents.x, half_extents.x), __randf_range(seed, -half_extents.y, half_extents.y), __randf_range(seed, -half_extents.z, half_extents.z));\n"; + code += " vec3 half_extents = extents / 2.0;\n"; + code += " return vec3(__randf_range(seed, -half_extents.x, half_extents.x), __randf_range(seed, -half_extents.y, half_extents.y), __randf_range(seed, -half_extents.z, half_extents.z));\n"; code += "}\n\n"; return code; } String VisualShaderNodeParticleBoxEmitter::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { String code; - code += "\t" + p_output_vars[0] + " = __get_random_point_in_box(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ");\n"; + code += " " + p_output_vars[0] + " = __get_random_point_in_box(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ");\n"; return code; } @@ -163,16 +163,16 @@ String VisualShaderNodeParticleRingEmitter::get_input_port_name(int p_port) cons String VisualShaderNodeParticleRingEmitter::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { String code; code += "vec3 __get_random_point_on_ring(inout uint seed, float radius, float inner_radius, float height) {\n"; - code += "\tfloat angle = __rand_from_seed(seed) * PI * 2.0;\n"; - code += "\tvec2 ring = vec2(sin(angle), cos(angle)) * __randf_range(seed, inner_radius, radius);\n"; - code += "\treturn vec3(ring.x, __randf_range(seed, min(0.0, height), max(0.0, height)), ring.y);\n"; + code += " float angle = __rand_from_seed(seed) * PI * 2.0;\n"; + code += " vec2 ring = vec2(sin(angle), cos(angle)) * __randf_range(seed, inner_radius, radius);\n"; + code += " return vec3(ring.x, __randf_range(seed, min(0.0, height), max(0.0, height)), ring.y);\n"; code += "}\n\n"; return code; } String VisualShaderNodeParticleRingEmitter::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { String code; - code = "\t" + p_output_vars[0] + " = __get_random_point_on_ring(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ", " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ", " + (p_input_vars[2].is_empty() ? (String)get_input_port_default_value(2) : p_input_vars[2]) + ");\n"; + code = " " + p_output_vars[0] + " = __get_random_point_on_ring(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ", " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ", " + (p_input_vars[2].is_empty() ? (String)get_input_port_default_value(2) : p_input_vars[2]) + ");\n"; return code; } @@ -242,9 +242,9 @@ String VisualShaderNodeParticleMultiplyByAxisAngle::get_output_port_name(int p_p String VisualShaderNodeParticleMultiplyByAxisAngle::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; if (degrees_mode) { - code += "\t" + p_output_vars[0] + " = __build_rotation_mat3(" + (p_input_vars[1].is_empty() ? ("vec3" + (String)get_input_port_default_value(1)) : p_input_vars[1]) + ", radians(" + (p_input_vars[2].is_empty() ? (String)get_input_port_default_value(2) : p_input_vars[2]) + ")) * " + (p_input_vars[0].is_empty() ? "vec3(0.0)" : p_input_vars[0]) + ";\n"; + code += " " + p_output_vars[0] + " = __build_rotation_mat3(" + (p_input_vars[1].is_empty() ? ("vec3" + (String)get_input_port_default_value(1)) : p_input_vars[1]) + ", radians(" + (p_input_vars[2].is_empty() ? (String)get_input_port_default_value(2) : p_input_vars[2]) + ")) * " + (p_input_vars[0].is_empty() ? "vec3(0.0)" : p_input_vars[0]) + ";\n"; } else { - code += "\t" + p_output_vars[0] + " = __build_rotation_mat3(" + (p_input_vars[1].is_empty() ? ("vec3" + (String)get_input_port_default_value(1)) : p_input_vars[1]) + ", " + (p_input_vars[2].is_empty() ? (String)get_input_port_default_value(2) : p_input_vars[2]) + ") * " + (p_input_vars[0].is_empty() ? "vec3(0.0)" : p_input_vars[0]) + ";\n"; + code += " " + p_output_vars[0] + " = __build_rotation_mat3(" + (p_input_vars[1].is_empty() ? ("vec3" + (String)get_input_port_default_value(1)) : p_input_vars[1]) + ", " + (p_input_vars[2].is_empty() ? (String)get_input_port_default_value(2) : p_input_vars[2]) + ") * " + (p_input_vars[0].is_empty() ? "vec3(0.0)" : p_input_vars[0]) + ";\n"; } return code; } @@ -315,16 +315,16 @@ String VisualShaderNodeParticleConeVelocity::get_output_port_name(int p_port) co String VisualShaderNodeParticleConeVelocity::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 += "\t__radians = radians(" + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ");\n"; - code += "\t__scalar_buff1 = __rand_from_seed_m1_p1(__seed) * __radians;\n"; - code += "\t__scalar_buff2 = __rand_from_seed_m1_p1(__seed) * __radians;\n"; - code += "\t__vec3_buff1 = " + (p_input_vars[0].is_empty() ? "vec3" + (String)get_input_port_default_value(0) : p_input_vars[0]) + ";\n"; - code += "\t__scalar_buff1 += __vec3_buff1.z != 0.0 ? atan(__vec3_buff1.x, __vec3_buff1.z) : sign(__vec3_buff1.x) * (PI / 2.0);\n"; - code += "\t__scalar_buff2 += __vec3_buff1.z != 0.0 ? atan(__vec3_buff1.y, abs(__vec3_buff1.z)) : (__vec3_buff1.x != 0.0 ? atan(__vec3_buff1.y, abs(__vec3_buff1.x)) : sign(__vec3_buff1.y) * (PI / 2.0));\n"; - code += "\t__vec3_buff1 = vec3(sin(__scalar_buff1), 0.0, cos(__scalar_buff1));\n"; - code += "\t__vec3_buff2 = vec3(0.0, sin(__scalar_buff2), cos(__scalar_buff2));\n"; - code += "\t__vec3_buff2.z = __vec3_buff2.z / max(0.0001, sqrt(abs(__vec3_buff2.z)));\n"; - code += "\t" + p_output_vars[0] + " = normalize(vec3(__vec3_buff1.x * __vec3_buff2.z, __vec3_buff2.y, __vec3_buff1.z * __vec3_buff2.z));\n"; + code += " __radians = radians(" + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ");\n"; + code += " __scalar_buff1 = __rand_from_seed_m1_p1(__seed) * __radians;\n"; + code += " __scalar_buff2 = __rand_from_seed_m1_p1(__seed) * __radians;\n"; + code += " __vec3_buff1 = " + (p_input_vars[0].is_empty() ? "vec3" + (String)get_input_port_default_value(0) : p_input_vars[0]) + ";\n"; + code += " __scalar_buff1 += __vec3_buff1.z != 0.0 ? atan(__vec3_buff1.x, __vec3_buff1.z) : sign(__vec3_buff1.x) * (PI / 2.0);\n"; + code += " __scalar_buff2 += __vec3_buff1.z != 0.0 ? atan(__vec3_buff1.y, abs(__vec3_buff1.z)) : (__vec3_buff1.x != 0.0 ? atan(__vec3_buff1.y, abs(__vec3_buff1.x)) : sign(__vec3_buff1.y) * (PI / 2.0));\n"; + code += " __vec3_buff1 = vec3(sin(__scalar_buff1), 0.0, cos(__scalar_buff1));\n"; + code += " __vec3_buff2 = vec3(0.0, sin(__scalar_buff2), cos(__scalar_buff2));\n"; + code += " __vec3_buff2.z = __vec3_buff2.z / max(0.0001, sqrt(abs(__vec3_buff2.z)));\n"; + code += " " + p_output_vars[0] + " = normalize(vec3(__vec3_buff1.x * __vec3_buff2.z, __vec3_buff2.y, __vec3_buff1.z * __vec3_buff2.z));\n"; return code; } @@ -394,23 +394,24 @@ String VisualShaderNodeParticleRandomness::get_input_port_name(int p_port) const String VisualShaderNodeParticleRandomness::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; if (op_type == OP_TYPE_SCALAR) { - code += vformat("\t%s = __randf_range(__seed, %s, %s);\n", p_output_vars[0], p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0], p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]); + code += vformat(" %s = __randf_range(__seed, %s, %s);\n", p_output_vars[0], p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0], p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]); } else if (op_type == OP_TYPE_VECTOR) { - code += vformat("\t%s = __randv_range(__seed, %s, %s);\n", p_output_vars[0], p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0], p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]); + code += vformat(" %s = __randv_range(__seed, %s, %s);\n", p_output_vars[0], p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0], p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]); } return code; } void VisualShaderNodeParticleRandomness::set_op_type(OpType p_op_type) { - ERR_FAIL_INDEX((int)p_op_type, OP_TYPE_MAX); - if (p_op_type != op_type) { - if (p_op_type == OP_TYPE_SCALAR) { - set_input_port_default_value(0, 0.0); - set_input_port_default_value(1, 1.0); - } else { - set_input_port_default_value(0, Vector3(-1.0, -1.0, -1.0)); - set_input_port_default_value(1, Vector3(1.0, 1.0, 1.0)); - } + ERR_FAIL_INDEX(int(p_op_type), int(OP_TYPE_MAX)); + if (op_type == p_op_type) { + return; + } + if (p_op_type == OP_TYPE_SCALAR) { + set_input_port_default_value(0, 0.0); + set_input_port_default_value(1, 1.0); + } else { + set_input_port_default_value(0, Vector3(-1.0, -1.0, -1.0)); + set_input_port_default_value(1, Vector3(1.0, 1.0, 1.0)); } op_type = p_op_type; emit_changed(); @@ -491,16 +492,14 @@ String VisualShaderNodeParticleAccelerator::generate_code(Shader::Mode p_mode, V String code; switch (mode) { case MODE_LINEAR: - code += "\t" + p_output_vars[0] + " = length(VELOCITY) > 0.0 ? " + "normalize(VELOCITY) * " + (p_input_vars[0].is_empty() ? "vec3" + (String)get_input_port_default_value(0) : p_input_vars[0]) + " * mix(1.0, __rand_from_seed(__seed), " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ") : vec3(0.0);\n"; + code += " " + p_output_vars[0] + " = length(VELOCITY) > 0.0 ? " + "normalize(VELOCITY) * " + (p_input_vars[0].is_empty() ? "vec3" + (String)get_input_port_default_value(0) : p_input_vars[0]) + " * mix(1.0, __rand_from_seed(__seed), " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ") : vec3(0.0);\n"; break; case MODE_RADIAL: - code += "\t" + p_output_vars[0] + " = length(__diff) > 0.0 ? __ndiff * " + (p_input_vars[0].is_empty() ? "vec3" + (String)get_input_port_default_value(0) : p_input_vars[0]) + " * mix(1.0, __rand_from_seed(__seed), " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ") : vec3(0.0);\n"; + code += " " + p_output_vars[0] + " = length(__diff) > 0.0 ? __ndiff * " + (p_input_vars[0].is_empty() ? "vec3" + (String)get_input_port_default_value(0) : p_input_vars[0]) + " * mix(1.0, __rand_from_seed(__seed), " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ") : vec3(0.0);\n"; break; case MODE_TANGENTIAL: - code += "\t__vec3_buff1 = cross(__ndiff, normalize(" + (p_input_vars[2].is_empty() ? "vec3" + (String)get_input_port_default_value(2) : p_input_vars[2]) + "));\n"; - code += "\t" + p_output_vars[0] + " = length(__vec3_buff1) > 0.0 ? normalize(__vec3_buff1) * (" + (p_input_vars[0].is_empty() ? "vec3" + (String)get_input_port_default_value(0) : p_input_vars[0]) + " * mix(1.0, __rand_from_seed(__seed), " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ")) : vec3(0.0);\n"; - break; - case MODE_MAX: + code += " __vec3_buff1 = cross(__ndiff, normalize(" + (p_input_vars[2].is_empty() ? "vec3" + (String)get_input_port_default_value(2) : p_input_vars[2]) + "));\n"; + code += " " + p_output_vars[0] + " = length(__vec3_buff1) > 0.0 ? normalize(__vec3_buff1) * (" + (p_input_vars[0].is_empty() ? "vec3" + (String)get_input_port_default_value(0) : p_input_vars[0]) + " * mix(1.0, __rand_from_seed(__seed), " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ")) : vec3(0.0);\n"; break; default: break; @@ -510,6 +509,10 @@ String VisualShaderNodeParticleAccelerator::generate_code(Shader::Mode p_mode, V } void VisualShaderNodeParticleAccelerator::set_mode(Mode p_mode) { + ERR_FAIL_INDEX(int(p_mode), int(MODE_MAX)); + if (mode == p_mode) { + return; + } mode = p_mode; emit_changed(); } @@ -693,7 +696,7 @@ bool VisualShaderNodeParticleOutput::is_port_separator(int p_index) const { String VisualShaderNodeParticleOutput::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; - String tab = "\t"; + String tab = " "; if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) { if (!p_input_vars[0].is_empty()) { // custom.rgb @@ -718,7 +721,7 @@ String VisualShaderNodeParticleOutput::generate_code(Shader::Mode p_mode, Visual if (!p_input_vars[0].is_empty()) { // active (begin) code += tab + "ACTIVE = " + p_input_vars[0] + ";\n"; code += tab + "if(ACTIVE) {\n"; - tab += "\t"; + tab += " "; } if (!p_input_vars[1].is_empty()) { // velocity code += tab + "VELOCITY = " + p_input_vars[1] + ";\n"; @@ -734,14 +737,14 @@ String VisualShaderNodeParticleOutput::generate_code(Shader::Mode p_mode, Visual if (shader_type == VisualShader::TYPE_START) { code += tab + "if (RESTART_POSITION) {\n"; if (!p_input_vars[4].is_empty()) { - code += tab + "\tTRANSFORM = mat4(vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(" + p_input_vars[4] + ", 1.0));\n"; + code += tab + " TRANSFORM = mat4(vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(" + p_input_vars[4] + ", 1.0));\n"; } else { - code += tab + "\tTRANSFORM = mat4(vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; + code += tab + " TRANSFORM = mat4(vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; } - code += tab + "\tif (RESTART_VELOCITY) {\n"; - code += tab + "\t\tVELOCITY = (EMISSION_TRANSFORM * vec4(VELOCITY, 0.0)).xyz;\n"; - code += tab + "\t}\n"; - code += tab + "\tTRANSFORM = EMISSION_TRANSFORM * TRANSFORM;\n"; + code += tab + " if (RESTART_VELOCITY) {\n"; + code += tab + " VELOCITY = (EMISSION_TRANSFORM * vec4(VELOCITY, 0.0)).xyz;\n"; + code += tab + " }\n"; + code += tab + " TRANSFORM = EMISSION_TRANSFORM * TRANSFORM;\n"; code += tab + "}\n"; } else if (shader_type == VisualShader::TYPE_COLLIDE) { // position if (!p_input_vars[4].is_empty()) { @@ -779,7 +782,7 @@ String VisualShaderNodeParticleOutput::generate_code(Shader::Mode p_mode, Visual } } if (!p_input_vars[0].is_empty()) { // active (end) - code += "\t}\n"; + code += " }\n"; } } return code; @@ -926,12 +929,12 @@ String VisualShaderNodeParticleEmit::generate_code(Shader::Mode p_mode, VisualSh if (!is_input_port_connected(0)) { default_condition = true; if (get_input_port_default_value(0)) { - tab = "\t"; + tab = " "; } else { return code; } } else { - tab = "\t\t"; + tab = " "; } String transform; @@ -1008,13 +1011,13 @@ String VisualShaderNodeParticleEmit::generate_code(Shader::Mode p_mode, VisualSh } if (!default_condition) { - code += "\tif (" + p_input_vars[0] + ") {\n"; + code += " if (" + p_input_vars[0] + ") {\n"; } code += tab + "emit_subparticle(" + transform + ", " + velocity + ", vec4(" + color + ", " + alpha + "), vec4(" + custom + ", " + custom_alpha + "), " + flags + ");\n"; if (!default_condition) { - code += "\t}\n"; + code += " }\n"; } return code; diff --git a/scene/resources/visual_shader_particle_nodes.h b/scene/resources/visual_shader_particle_nodes.h index ecd187a885..f5435c3511 100644 --- a/scene/resources/visual_shader_particle_nodes.h +++ b/scene/resources/visual_shader_particle_nodes.h @@ -181,8 +181,8 @@ VARIANT_ENUM_CAST(VisualShaderNodeParticleRandomness::OpType) // Process nodes -class VisualShaderNodeParticleAccelerator : public VisualShaderNodeOutput { - GDCLASS(VisualShaderNodeParticleAccelerator, VisualShaderNodeOutput); +class VisualShaderNodeParticleAccelerator : public VisualShaderNode { + GDCLASS(VisualShaderNodeParticleAccelerator, VisualShaderNode); public: enum Mode { diff --git a/scene/resources/visual_shader_sdf_nodes.cpp b/scene/resources/visual_shader_sdf_nodes.cpp index d25e32b070..14c655b129 100644 --- a/scene/resources/visual_shader_sdf_nodes.cpp +++ b/scene/resources/visual_shader_sdf_nodes.cpp @@ -61,7 +61,7 @@ String VisualShaderNodeSDFToScreenUV::get_output_port_name(int p_port) const { } String VisualShaderNodeSDFToScreenUV::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 "\t" + p_output_vars[0] + " = vec3(sdf_to_screen_uv(" + (p_input_vars[0] == String() ? "vec2(0.0)" : p_input_vars[0] + ".xy") + "), 0.0f);\n"; + return " " + p_output_vars[0] + " = vec3(sdf_to_screen_uv(" + (p_input_vars[0] == String() ? "vec2(0.0)" : p_input_vars[0] + ".xy") + "), 0.0f);\n"; } VisualShaderNodeSDFToScreenUV::VisualShaderNodeSDFToScreenUV() { @@ -105,7 +105,7 @@ String VisualShaderNodeScreenUVToSDF::get_input_port_default_hint(int p_port) co } String VisualShaderNodeScreenUVToSDF::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 "\t" + p_output_vars[0] + " = vec3(screen_uv_to_sdf(" + (p_input_vars[0] == String() ? "SCREEN_UV" : p_input_vars[0] + ".xy") + "), 0.0f);\n"; + return " " + p_output_vars[0] + " = vec3(screen_uv_to_sdf(" + (p_input_vars[0] == String() ? "SCREEN_UV" : p_input_vars[0] + ".xy") + "), 0.0f);\n"; } VisualShaderNodeScreenUVToSDF::VisualShaderNodeScreenUVToSDF() { @@ -142,7 +142,7 @@ String VisualShaderNodeTextureSDF::get_output_port_name(int p_port) const { } String VisualShaderNodeTextureSDF::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 "\t" + p_output_vars[0] + " = texture_sdf(" + (p_input_vars[0] == String() ? "vec2(0.0)" : p_input_vars[0] + ".xy") + ");\n"; + return " " + p_output_vars[0] + " = texture_sdf(" + (p_input_vars[0] == String() ? "vec2(0.0)" : p_input_vars[0] + ".xy") + ");\n"; } VisualShaderNodeTextureSDF::VisualShaderNodeTextureSDF() { @@ -179,7 +179,7 @@ String VisualShaderNodeTextureSDFNormal::get_output_port_name(int p_port) const } String VisualShaderNodeTextureSDFNormal::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 "\t" + p_output_vars[0] + " = vec3(texture_sdf_normal(" + (p_input_vars[0] == String() ? "vec2(0.0)" : p_input_vars[0] + ".xy") + "), 0.0f);\n"; + return " " + p_output_vars[0] + " = vec3(texture_sdf_normal(" + (p_input_vars[0] == String() ? "vec2(0.0)" : p_input_vars[0] + ".xy") + "), 0.0f);\n"; } VisualShaderNodeTextureSDFNormal::VisualShaderNodeTextureSDFNormal() { @@ -240,40 +240,40 @@ String VisualShaderNodeSDFRaymarch::get_output_port_name(int p_port) const { String VisualShaderNodeSDFRaymarch::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 += "\t{\n"; + code += " {\n"; if (p_input_vars[0] == String()) { - code += "\t\tvec2 __from_pos = vec2(0.0f);\n"; + code += " vec2 __from_pos = vec2(0.0f);\n"; } else { - code += "\t\tvec2 __from_pos = " + p_input_vars[0] + ".xy;\n"; + code += " vec2 __from_pos = " + p_input_vars[0] + ".xy;\n"; } if (p_input_vars[1] == String()) { - code += "\t\tvec2 __to_pos = vec2(0.0f);\n"; + code += " vec2 __to_pos = vec2(0.0f);\n"; } else { - code += "\t\tvec2 __to_pos = " + p_input_vars[1] + ".xy;\n"; + code += " vec2 __to_pos = " + p_input_vars[1] + ".xy;\n"; } - code += "\n\t\tvec2 __at = __from_pos;\n"; - code += "\t\tfloat __max_dist = distance(__from_pos, __to_pos);\n"; - code += "\t\tvec2 __dir = normalize(__to_pos - __from_pos);\n\n"; - - code += "\t\tfloat __accum = 0.0f;\n"; - code += "\t\twhile(__accum < __max_dist) {\n"; - code += "\t\t\tfloat __d = texture_sdf(__at);\n"; - code += "\t\t\t__accum += __d;\n"; - code += "\t\t\tif (__d < 0.01f) {\n"; - code += "\t\t\t\tbreak;\n"; - code += "\t\t\t}\n"; - code += "\t\t\t__at += __d * __dir;\n"; - code += "\t\t}\n"; - - code += "\t\tfloat __dist = min(__max_dist, __accum);\n"; - code += "\t\t" + p_output_vars[0] + " = __dist;\n"; - code += "\t\t" + p_output_vars[1] + " = __accum < __max_dist;\n"; - code += "\t\t" + p_output_vars[2] + " = vec3(__from_pos + __dir * __dist, 0.0f);\n"; - - code += "\t}\n"; + code += "\n vec2 __at = __from_pos;\n"; + code += " float __max_dist = distance(__from_pos, __to_pos);\n"; + code += " vec2 __dir = normalize(__to_pos - __from_pos);\n\n"; + + code += " float __accum = 0.0f;\n"; + code += " while(__accum < __max_dist) {\n"; + code += " float __d = texture_sdf(__at);\n"; + code += " __accum += __d;\n"; + code += " if (__d < 0.01f) {\n"; + code += " break;\n"; + code += " }\n"; + code += " __at += __d * __dir;\n"; + code += " }\n"; + + code += " float __dist = min(__max_dist, __accum);\n"; + code += " " + p_output_vars[0] + " = __dist;\n"; + code += " " + p_output_vars[1] + " = __accum < __max_dist;\n"; + code += " " + p_output_vars[2] + " = vec3(__from_pos + __dir * __dist, 0.0f);\n"; + + code += " }\n"; return code; } diff --git a/scene/resources/line_shape_2d.cpp b/scene/resources/world_boundary_shape_2d.cpp index d206f12287..39af92793f 100644 --- a/scene/resources/line_shape_2d.cpp +++ b/scene/resources/world_boundary_shape_2d.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* line_shape_2d.cpp */ +/* world_boundary_shape_2d.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,13 +28,13 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "line_shape_2d.h" +#include "world_boundary_shape_2d.h" #include "core/math/geometry_2d.h" #include "servers/physics_server_2d.h" #include "servers/rendering_server.h" -bool LineShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { +bool WorldBoundaryShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { Vector2 point = get_distance() * get_normal(); Vector2 l[2][2] = { { point - get_normal().orthogonal() * 100, point + get_normal().orthogonal() * 100 }, { point, point + get_normal() * 30 } }; @@ -48,7 +48,7 @@ bool LineShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tol return false; } -void LineShape2D::_update_shape() { +void WorldBoundaryShape2D::_update_shape() { Array arr; arr.push_back(normal); arr.push_back(distance); @@ -56,25 +56,25 @@ void LineShape2D::_update_shape() { emit_changed(); } -void LineShape2D::set_normal(const Vector2 &p_normal) { +void WorldBoundaryShape2D::set_normal(const Vector2 &p_normal) { normal = p_normal; _update_shape(); } -void LineShape2D::set_distance(real_t p_distance) { +void WorldBoundaryShape2D::set_distance(real_t p_distance) { distance = p_distance; _update_shape(); } -Vector2 LineShape2D::get_normal() const { +Vector2 WorldBoundaryShape2D::get_normal() const { return normal; } -real_t LineShape2D::get_distance() const { +real_t WorldBoundaryShape2D::get_distance() const { return distance; } -void LineShape2D::draw(const RID &p_to_rid, const Color &p_color) { +void WorldBoundaryShape2D::draw(const RID &p_to_rid, const Color &p_color) { Vector2 point = get_distance() * get_normal(); Vector2 l1[2] = { point - get_normal().orthogonal() * 100, point + get_normal().orthogonal() * 100 }; @@ -83,7 +83,7 @@ void LineShape2D::draw(const RID &p_to_rid, const Color &p_color) { RS::get_singleton()->canvas_item_add_line(p_to_rid, l2[0], l2[1], p_color, 3); } -Rect2 LineShape2D::get_rect() const { +Rect2 WorldBoundaryShape2D::get_rect() const { Vector2 point = get_distance() * get_normal(); Vector2 l1[2] = { point - get_normal().orthogonal() * 100, point + get_normal().orthogonal() * 100 }; @@ -96,22 +96,22 @@ Rect2 LineShape2D::get_rect() const { return rect; } -real_t LineShape2D::get_enclosing_radius() const { +real_t WorldBoundaryShape2D::get_enclosing_radius() const { return distance; } -void LineShape2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_normal", "normal"), &LineShape2D::set_normal); - ClassDB::bind_method(D_METHOD("get_normal"), &LineShape2D::get_normal); +void WorldBoundaryShape2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_normal", "normal"), &WorldBoundaryShape2D::set_normal); + ClassDB::bind_method(D_METHOD("get_normal"), &WorldBoundaryShape2D::get_normal); - ClassDB::bind_method(D_METHOD("set_distance", "distance"), &LineShape2D::set_distance); - ClassDB::bind_method(D_METHOD("get_distance"), &LineShape2D::get_distance); + ClassDB::bind_method(D_METHOD("set_distance", "distance"), &WorldBoundaryShape2D::set_distance); + 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"), "set_distance", "get_distance"); } -LineShape2D::LineShape2D() : - Shape2D(PhysicsServer2D::get_singleton()->line_shape_create()) { +WorldBoundaryShape2D::WorldBoundaryShape2D() : + Shape2D(PhysicsServer2D::get_singleton()->world_boundary_shape_create()) { _update_shape(); } diff --git a/scene/resources/line_shape_2d.h b/scene/resources/world_boundary_shape_2d.h index 210a1aa9e6..4cc60f5985 100644 --- a/scene/resources/line_shape_2d.h +++ b/scene/resources/world_boundary_shape_2d.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* line_shape_2d.h */ +/* world_boundary_shape_2d.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,15 +28,15 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef LINE_SHAPE_2D_H -#define LINE_SHAPE_2D_H +#ifndef WORLD_BOUNDARY_SHAPE_2D_H +#define WORLD_BOUNDARY_SHAPE_2D_H #include "scene/resources/shape_2d.h" -class LineShape2D : public Shape2D { - GDCLASS(LineShape2D, Shape2D); +class WorldBoundaryShape2D : public Shape2D { + GDCLASS(WorldBoundaryShape2D, Shape2D); - // LineShape2D is often used for one-way platforms, where the normal pointing up makes sense. + // WorldBoundaryShape2D is often used for one-way platforms, where the normal pointing up makes sense. Vector2 normal = Vector2(0, -1); real_t distance = 0.0; @@ -58,7 +58,7 @@ public: virtual Rect2 get_rect() const override; virtual real_t get_enclosing_radius() const override; - LineShape2D(); + WorldBoundaryShape2D(); }; -#endif // LINE_SHAPE_2D_H +#endif // WORLD_BOUNDARY_SHAPE_2D_H diff --git a/scene/resources/world_margin_shape_3d.cpp b/scene/resources/world_boundary_shape_3d.cpp index 28d50e1921..8cde537164 100644 --- a/scene/resources/world_margin_shape_3d.cpp +++ b/scene/resources/world_boundary_shape_3d.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* world_margin_shape_3d.cpp */ +/* world_boundary_shape_3d.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,11 +28,11 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "world_margin_shape_3d.h" +#include "world_boundary_shape_3d.h" #include "servers/physics_server_3d.h" -Vector<Vector3> WorldMarginShape3D::get_debug_mesh_lines() const { +Vector<Vector3> WorldBoundaryShape3D::get_debug_mesh_lines() const { Plane p = get_plane(); Vector<Vector3> points; @@ -60,29 +60,29 @@ Vector<Vector3> WorldMarginShape3D::get_debug_mesh_lines() const { return points; } -void WorldMarginShape3D::_update_shape() { +void WorldBoundaryShape3D::_update_shape() { PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), plane); Shape3D::_update_shape(); } -void WorldMarginShape3D::set_plane(Plane p_plane) { +void WorldBoundaryShape3D::set_plane(const Plane &p_plane) { plane = p_plane; _update_shape(); notify_change_to_owners(); } -Plane WorldMarginShape3D::get_plane() const { +const Plane &WorldBoundaryShape3D::get_plane() const { return plane; } -void WorldMarginShape3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_plane", "plane"), &WorldMarginShape3D::set_plane); - ClassDB::bind_method(D_METHOD("get_plane"), &WorldMarginShape3D::get_plane); +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"); } -WorldMarginShape3D::WorldMarginShape3D() : - Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_PLANE)) { +WorldBoundaryShape3D::WorldBoundaryShape3D() : + Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_WORLD_BOUNDARY)) { set_plane(Plane(0, 1, 0, 0)); } diff --git a/scene/resources/world_margin_shape_3d.h b/scene/resources/world_boundary_shape_3d.h index 00417c4408..853f555ebc 100644 --- a/scene/resources/world_margin_shape_3d.h +++ b/scene/resources/world_boundary_shape_3d.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* world_margin_shape_3d.h */ +/* world_boundary_shape_3d.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,13 +28,13 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef WORLD_MARGIN_SHAPE_3D_H -#define WORLD_MARGIN_SHAPE_3D_H +#ifndef WORLD_BOUNDARY_SHAPE_3D_H +#define WORLD_BOUNDARY_SHAPE_3D_H #include "scene/resources/shape_3d.h" -class WorldMarginShape3D : public Shape3D { - GDCLASS(WorldMarginShape3D, Shape3D); +class WorldBoundaryShape3D : public Shape3D { + GDCLASS(WorldBoundaryShape3D, Shape3D); Plane plane; protected: @@ -42,8 +42,8 @@ protected: virtual void _update_shape() override; public: - void set_plane(Plane p_plane); - Plane get_plane() const; + void set_plane(const Plane &p_plane); + const Plane &get_plane() const; virtual Vector<Vector3> get_debug_mesh_lines() const override; virtual real_t get_enclosing_radius() const override { @@ -51,6 +51,6 @@ public: return 0; } - WorldMarginShape3D(); + WorldBoundaryShape3D(); }; -#endif // WORLD_MARGIN_SHAPE_H +#endif // WORLD_BOUNDARY_SHAPE_H diff --git a/scene/scene_string_names.cpp b/scene/scene_string_names.cpp index 35f8a506fc..5d89d295c2 100644 --- a/scene/scene_string_names.cpp +++ b/scene/scene_string_names.cpp @@ -63,9 +63,12 @@ SceneStringNames::SceneStringNames() { animation_started = StaticCString::create("animation_started"); pose_updated = StaticCString::create("pose_updated"); + bone_pose_changed = StaticCString::create("bone_pose_changed"); mouse_entered = StaticCString::create("mouse_entered"); mouse_exited = StaticCString::create("mouse_exited"); + mouse_shape_entered = StaticCString::create("mouse_shape_entered"); + mouse_shape_exited = StaticCString::create("mouse_shape_exited"); focus_entered = StaticCString::create("focus_entered"); focus_exited = StaticCString::create("focus_exited"); @@ -134,6 +137,7 @@ SceneStringNames::SceneStringNames() { _spatial_editor_group = StaticCString::create("_spatial_editor_group"); _request_gizmo = StaticCString::create("_request_gizmo"); + _clear_subgizmo_selection = StaticCString::create("_clear_subgizmo_selection"); offset = StaticCString::create("offset"); unit_offset = StaticCString::create("unit_offset"); @@ -169,6 +173,8 @@ SceneStringNames::SceneStringNames() { _mouse_enter = StaticCString::create("_mouse_enter"); _mouse_exit = StaticCString::create("_mouse_exit"); + _mouse_shape_enter = StaticCString::create("_mouse_shape_enter"); + _mouse_shape_exit = StaticCString::create("_mouse_shape_exit"); _pressed = StaticCString::create("_pressed"); _toggled = StaticCString::create("_toggled"); diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h index 01865b0d2f..01f427ecd1 100644 --- a/scene/scene_string_names.h +++ b/scene/scene_string_names.h @@ -84,6 +84,8 @@ public: StringName mouse_entered; StringName mouse_exited; + StringName mouse_shape_entered; + StringName mouse_shape_exited; StringName focus_entered; StringName focus_exited; @@ -96,6 +98,7 @@ public: StringName animation_started; StringName pose_updated; + StringName bone_pose_changed; StringName body_shape_entered; StringName body_entered; @@ -154,6 +157,7 @@ public: StringName _spatial_editor_group; StringName _request_gizmo; + StringName _clear_subgizmo_selection; StringName offset; StringName unit_offset; @@ -181,6 +185,8 @@ public: StringName _mouse_enter; StringName _mouse_exit; + StringName _mouse_shape_enter; + StringName _mouse_shape_exit; StringName frame_changed; StringName texture_changed; |