diff options
Diffstat (limited to 'scene/2d')
44 files changed, 3186 insertions, 1357 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/2d/listener_2d.cpp b/scene/2d/listener_2d.cpp new file mode 100644 index 0000000000..444f05f2b1 --- /dev/null +++ b/scene/2d/listener_2d.cpp @@ -0,0 +1,112 @@ +/*************************************************************************/ +/* listener_2d.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "listener_2d.h" + +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; +} + +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; +} + +void Listener2D::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back(PropertyInfo(Variant::BOOL, "current")); +} + +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; + } +} + +void Listener2D::make_current() { + current = true; + if (!is_inside_tree()) { + return; + } + get_viewport()->_listener_2d_set(this); +} + +void Listener2D::clear_current() { + current = false; + if (!is_inside_tree()) { + return; + } + get_viewport()->_listener_2d_remove(this); +} + +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; +} + +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/2d/listener_2d.h b/scene/2d/listener_2d.h new file mode 100644 index 0000000000..0289a8087d --- /dev/null +++ b/scene/2d/listener_2d.h @@ -0,0 +1,61 @@ +/*************************************************************************/ +/* listener_2d.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef LISTENER_2D_H +#define LISTENER_2D_H + +#include "scene/2d/node_2d.h" +#include "scene/main/window.h" + +class Listener2D : public Node2D { + GDCLASS(Listener2D, Node2D); + +private: + bool current = false; + + friend class Viewport; + +protected: + void _update_listener(); + + 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); + + static void _bind_methods(); + +public: + void make_current(); + void clear_current(); + bool is_current() const; +}; + +#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 |