diff options
Diffstat (limited to 'scene/2d')
78 files changed, 8790 insertions, 7348 deletions
diff --git a/scene/2d/animated_sprite_2d.cpp b/scene/2d/animated_sprite_2d.cpp index fc34f967ce..96a3134691 100644 --- a/scene/2d/animated_sprite_2d.cpp +++ b/scene/2d/animated_sprite_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,12 +30,9 @@ #include "animated_sprite_2d.h" -#include "core/os/os.h" +#include "scene/main/viewport.h" #include "scene/scene_string_names.h" -#define NORMAL_SUFFIX "_normal" -#define SPECULAR_SUFFIX "_specular" - #ifdef TOOLS_ENABLED Dictionary AnimatedSprite2D::_edit_get_state() const { Dictionary state = Node2D::_edit_get_state(); @@ -70,8 +67,9 @@ bool AnimatedSprite2D::_edit_use_rect() const { return false; } Ref<Texture2D> t; - if (animation) + if (animation) { t = frames->get_frame(animation, frame); + } return t.is_valid(); } #endif @@ -86,254 +84,31 @@ Rect2 AnimatedSprite2D::_get_rect() const { } Ref<Texture2D> t; - if (animation) + if (animation) { t = frames->get_frame(animation, frame); - if (t.is_null()) + } + if (t.is_null()) { return Rect2(); + } Size2 s = t->get_size(); Point2 ofs = offset; - if (centered) + if (centered) { ofs -= Size2(s) / 2; - - if (s == Size2(0, 0)) - s = Size2(1, 1); - - return Rect2(ofs, s); -} - -void SpriteFrames::add_frame(const StringName &p_anim, const Ref<Texture2D> &p_frame, int p_at_pos) { - - Map<StringName, Anim>::Element *E = animations.find(p_anim); - ERR_FAIL_COND_MSG(!E, "Animation '" + String(p_anim) + "' doesn't exist."); - - if (p_at_pos >= 0 && p_at_pos < E->get().frames.size()) - E->get().frames.insert(p_at_pos, p_frame); - else - E->get().frames.push_back(p_frame); - - emit_changed(); -} - -int SpriteFrames::get_frame_count(const StringName &p_anim) const { - const Map<StringName, Anim>::Element *E = animations.find(p_anim); - ERR_FAIL_COND_V_MSG(!E, 0, "Animation '" + String(p_anim) + "' doesn't exist."); - - return E->get().frames.size(); -} - -void SpriteFrames::remove_frame(const StringName &p_anim, int p_idx) { - - Map<StringName, Anim>::Element *E = animations.find(p_anim); - ERR_FAIL_COND_MSG(!E, "Animation '" + String(p_anim) + "' doesn't exist."); - - E->get().frames.remove(p_idx); - emit_changed(); -} -void SpriteFrames::clear(const StringName &p_anim) { - - Map<StringName, Anim>::Element *E = animations.find(p_anim); - ERR_FAIL_COND_MSG(!E, "Animation '" + String(p_anim) + "' doesn't exist."); - - E->get().frames.clear(); - emit_changed(); -} - -void SpriteFrames::clear_all() { - - animations.clear(); - add_animation("default"); -} - -void SpriteFrames::add_animation(const StringName &p_anim) { - - ERR_FAIL_COND_MSG(animations.has(p_anim), "SpriteFrames already has animation '" + p_anim + "'."); - - animations[p_anim] = Anim(); - animations[p_anim].normal_name = String(p_anim) + NORMAL_SUFFIX; - animations[p_anim].specular_name = String(p_anim) + SPECULAR_SUFFIX; -} - -bool SpriteFrames::has_animation(const StringName &p_anim) const { - - return animations.has(p_anim); -} -void SpriteFrames::remove_animation(const StringName &p_anim) { - - animations.erase(p_anim); -} - -void SpriteFrames::rename_animation(const StringName &p_prev, const StringName &p_next) { - - ERR_FAIL_COND_MSG(!animations.has(p_prev), "SpriteFrames doesn't have animation '" + String(p_prev) + "'."); - ERR_FAIL_COND_MSG(animations.has(p_next), "Animation '" + String(p_next) + "' already exists."); - - Anim anim = animations[p_prev]; - animations.erase(p_prev); - animations[p_next] = anim; - animations[p_next].normal_name = String(p_next) + NORMAL_SUFFIX; - animations[p_next].specular_name = String(p_next) + SPECULAR_SUFFIX; -} - -Vector<String> SpriteFrames::_get_animation_list() const { - - Vector<String> ret; - List<StringName> al; - get_animation_list(&al); - for (List<StringName>::Element *E = al.front(); E; E = E->next()) { - - ret.push_back(E->get()); - } - - return ret; -} - -void SpriteFrames::get_animation_list(List<StringName> *r_animations) const { - - for (const Map<StringName, Anim>::Element *E = animations.front(); E; E = E->next()) { - r_animations->push_back(E->key()); - } -} - -Vector<String> SpriteFrames::get_animation_names() const { - - Vector<String> names; - for (const Map<StringName, Anim>::Element *E = animations.front(); E; E = E->next()) { - names.push_back(E->key()); - } - names.sort(); - return names; -} - -void SpriteFrames::set_animation_speed(const StringName &p_anim, float p_fps) { - - ERR_FAIL_COND_MSG(p_fps < 0, "Animation speed cannot be negative (" + itos(p_fps) + ")."); - Map<StringName, Anim>::Element *E = animations.find(p_anim); - ERR_FAIL_COND_MSG(!E, "Animation '" + String(p_anim) + "' doesn't exist."); - E->get().speed = p_fps; -} -float SpriteFrames::get_animation_speed(const StringName &p_anim) const { - - const Map<StringName, Anim>::Element *E = animations.find(p_anim); - ERR_FAIL_COND_V_MSG(!E, 0, "Animation '" + String(p_anim) + "' doesn't exist."); - return E->get().speed; -} - -void SpriteFrames::set_animation_loop(const StringName &p_anim, bool p_loop) { - Map<StringName, Anim>::Element *E = animations.find(p_anim); - ERR_FAIL_COND_MSG(!E, "Animation '" + String(p_anim) + "' doesn't exist."); - E->get().loop = p_loop; -} -bool SpriteFrames::get_animation_loop(const StringName &p_anim) const { - const Map<StringName, Anim>::Element *E = animations.find(p_anim); - ERR_FAIL_COND_V_MSG(!E, false, "Animation '" + String(p_anim) + "' doesn't exist."); - return E->get().loop; -} - -void SpriteFrames::_set_frames(const Array &p_frames) { - - clear_all(); - Map<StringName, Anim>::Element *E = animations.find(SceneStringNames::get_singleton()->_default); - ERR_FAIL_COND(!E); - - E->get().frames.resize(p_frames.size()); - for (int i = 0; i < E->get().frames.size(); i++) - E->get().frames.write[i] = p_frames[i]; -} -Array SpriteFrames::_get_frames() const { - - return Array(); -} - -Array SpriteFrames::_get_animations() const { - - Array anims; - for (Map<StringName, Anim>::Element *E = animations.front(); E; E = E->next()) { - Dictionary d; - d["name"] = E->key(); - d["speed"] = E->get().speed; - d["loop"] = E->get().loop; - Array frames; - for (int i = 0; i < E->get().frames.size(); i++) { - frames.push_back(E->get().frames[i]); - } - d["frames"] = frames; - anims.push_back(d); } - return anims; -} -void SpriteFrames::_set_animations(const Array &p_animations) { - - animations.clear(); - for (int i = 0; i < p_animations.size(); i++) { - - Dictionary d = p_animations[i]; - - ERR_CONTINUE(!d.has("name")); - ERR_CONTINUE(!d.has("speed")); - ERR_CONTINUE(!d.has("loop")); - ERR_CONTINUE(!d.has("frames")); - - Anim anim; - anim.speed = d["speed"]; - anim.loop = d["loop"]; - Array frames = d["frames"]; - for (int j = 0; j < frames.size(); j++) { - - RES res = frames[j]; - anim.frames.push_back(res); - } - - animations[d["name"]] = anim; + if (s == Size2(0, 0)) { + s = Size2(1, 1); } -} - -void SpriteFrames::_bind_methods() { - - ClassDB::bind_method(D_METHOD("add_animation", "anim"), &SpriteFrames::add_animation); - ClassDB::bind_method(D_METHOD("has_animation", "anim"), &SpriteFrames::has_animation); - ClassDB::bind_method(D_METHOD("remove_animation", "anim"), &SpriteFrames::remove_animation); - ClassDB::bind_method(D_METHOD("rename_animation", "anim", "newname"), &SpriteFrames::rename_animation); - - ClassDB::bind_method(D_METHOD("get_animation_names"), &SpriteFrames::get_animation_names); - - ClassDB::bind_method(D_METHOD("set_animation_speed", "anim", "speed"), &SpriteFrames::set_animation_speed); - ClassDB::bind_method(D_METHOD("get_animation_speed", "anim"), &SpriteFrames::get_animation_speed); - - ClassDB::bind_method(D_METHOD("set_animation_loop", "anim", "loop"), &SpriteFrames::set_animation_loop); - ClassDB::bind_method(D_METHOD("get_animation_loop", "anim"), &SpriteFrames::get_animation_loop); - - ClassDB::bind_method(D_METHOD("add_frame", "anim", "frame", "at_position"), &SpriteFrames::add_frame, DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("get_frame_count", "anim"), &SpriteFrames::get_frame_count); - ClassDB::bind_method(D_METHOD("get_frame", "anim", "idx"), &SpriteFrames::get_frame); - ClassDB::bind_method(D_METHOD("set_frame", "anim", "idx", "txt"), &SpriteFrames::set_frame); - ClassDB::bind_method(D_METHOD("remove_frame", "anim", "idx"), &SpriteFrames::remove_frame); - ClassDB::bind_method(D_METHOD("clear", "anim"), &SpriteFrames::clear); - ClassDB::bind_method(D_METHOD("clear_all"), &SpriteFrames::clear_all); - - ClassDB::bind_method(D_METHOD("_set_frames"), &SpriteFrames::_set_frames); - ClassDB::bind_method(D_METHOD("_get_frames"), &SpriteFrames::_get_frames); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "frames", PROPERTY_HINT_NONE, "", 0), "_set_frames", "_get_frames"); //compatibility - - ClassDB::bind_method(D_METHOD("_set_animations"), &SpriteFrames::_set_animations); - ClassDB::bind_method(D_METHOD("_get_animations"), &SpriteFrames::_get_animations); - - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "animations", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_animations", "_get_animations"); //compatibility -} - -SpriteFrames::SpriteFrames() { - - add_animation(SceneStringNames::get_singleton()->_default); + return Rect2(ofs, s); } void AnimatedSprite2D::_validate_property(PropertyInfo &property) const { - - if (!frames.is_valid()) + if (!frames.is_valid()) { return; + } if (property.name == "animation") { - property.hint = PROPERTY_HINT_ENUM; List<StringName> names; frames->get_animation_list(&names); @@ -347,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; } } @@ -371,43 +146,45 @@ void AnimatedSprite2D::_validate_property(PropertyInfo &property) const { } void AnimatedSprite2D::_notification(int p_what) { - switch (p_what) { case NOTIFICATION_INTERNAL_PROCESS: { - - if (frames.is_null()) + if (frames.is_null()) { return; - if (!frames->has_animation(animation)) + } + if (!frames->has_animation(animation)) { return; - if (frame < 0) + } + if (frame < 0) { return; + } - float speed = frames->get_animation_speed(animation) * speed_scale; - if (speed == 0) + 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) { - timeout = _get_frame_duration(); int fc = frames->get_frame_count(animation); if ((!backwards && frame >= fc - 1) || (backwards && frame <= 0)) { if (frames->get_animation_loop(animation)) { - if (backwards) + if (backwards) { frame = fc - 1; - else + } else { frame = 0; + } emit_signal(SceneStringNames::get_singleton()->animation_finished); } else { - if (backwards) + if (backwards) { frame = 0; - else + } else { frame = fc - 1; + } if (!is_over) { is_over = true; @@ -415,70 +192,75 @@ void AnimatedSprite2D::_notification(int p_what) { } } } else { - if (backwards) + if (backwards) { frame--; - else + } else { frame++; + } } update(); - _change_notify("frame"); + 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; } } break; case NOTIFICATION_DRAW: { - - if (frames.is_null()) + if (frames.is_null()) { return; - if (frame < 0) + } + if (frame < 0) { return; - if (!frames->has_animation(animation)) + } + if (!frames->has_animation(animation)) { return; + } Ref<Texture2D> texture = frames->get_frame(animation, frame); - if (texture.is_null()) + if (texture.is_null()) { return; - - Ref<Texture2D> normal = frames->get_normal_frame(animation, frame); - Ref<Texture2D> specular = frames->get_specular_frame(animation, frame); + } RID ci = get_canvas_item(); Size2i s; s = texture->get_size(); Point2 ofs = offset; - if (centered) + if (centered) { ofs -= s / 2; + } - if (Engine::get_singleton()->get_use_pixel_snap()) { + if (get_viewport() && get_viewport()->is_snap_2d_transforms_to_pixel_enabled()) { ofs = ofs.floor(); } Rect2 dst_rect(ofs, s); - if (hflip) + if (hflip) { dst_rect.size.x = -dst_rect.size.x; - if (vflip) + } + if (vflip) { dst_rect.size.y = -dst_rect.size.y; + } - texture->draw_rect_region(ci, dst_rect, Rect2(Vector2(), texture->get_size()), Color(1, 1, 1), false, normal, specular, Color(specular_color.r, specular_color.g, specular_color.b, shininess)); + texture->draw_rect_region(ci, dst_rect, Rect2(Vector2(), texture->get_size()), Color(1, 1, 1), false); } break; } } void AnimatedSprite2D::set_sprite_frames(const Ref<SpriteFrames> &p_frames) { - - if (frames.is_valid()) + if (frames.is_valid()) { frames->disconnect("changed", callable_mp(this, &AnimatedSprite2D::_res_changed)); + } frames = p_frames; - if (frames.is_valid()) + if (frames.is_valid()) { frames->connect("changed", callable_mp(this, &AnimatedSprite2D::_res_changed)); + } if (!frames.is_valid()) { frame = 0; @@ -486,49 +268,49 @@ void AnimatedSprite2D::set_sprite_frames(const Ref<SpriteFrames> &p_frames) { set_frame(frame); } - _change_notify(); + notify_property_list_changed(); _reset_timeout(); update(); - update_configuration_warning(); + update_configuration_warnings(); } Ref<SpriteFrames> AnimatedSprite2D::get_sprite_frames() const { - return frames; } void AnimatedSprite2D::set_frame(int p_frame) { - if (!frames.is_valid()) { return; } if (frames->has_animation(animation)) { int limit = frames->get_frame_count(animation); - if (p_frame >= limit) + if (p_frame >= limit) { p_frame = limit - 1; + } } - if (p_frame < 0) + if (p_frame < 0) { p_frame = 0; + } - if (frame == p_frame) + if (frame == p_frame) { return; + } frame = p_frame; _reset_timeout(); update(); - _change_notify("frame"); + emit_signal(SceneStringNames::get_singleton()->frame_changed); } -int AnimatedSprite2D::get_frame() const { +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); @@ -537,103 +319,91 @@ 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; } void AnimatedSprite2D::set_centered(bool p_center) { - centered = p_center; update(); item_rect_changed(); } bool AnimatedSprite2D::is_centered() const { - return centered; } void AnimatedSprite2D::set_offset(const Point2 &p_offset) { - offset = p_offset; update(); item_rect_changed(); - _change_notify("offset"); } -Point2 AnimatedSprite2D::get_offset() const { +Point2 AnimatedSprite2D::get_offset() const { return offset; } void AnimatedSprite2D::set_flip_h(bool p_flip) { - hflip = p_flip; update(); } -bool AnimatedSprite2D::is_flipped_h() const { +bool AnimatedSprite2D::is_flipped_h() const { return hflip; } void AnimatedSprite2D::set_flip_v(bool p_flip) { - vflip = p_flip; update(); } -bool AnimatedSprite2D::is_flipped_v() const { +bool AnimatedSprite2D::is_flipped_v() const { return vflip; } void AnimatedSprite2D::_res_changed() { - set_frame(frame); - _change_notify("frame"); - _change_notify("animation"); + update(); } void AnimatedSprite2D::_set_playing(bool p_playing) { - - if (playing == p_playing) + if (playing == p_playing) { return; + } playing = p_playing; _reset_timeout(); set_process_internal(playing); } bool AnimatedSprite2D::_is_playing() const { - return playing; } void AnimatedSprite2D::play(const StringName &p_animation, const bool p_backwards) { - backwards = p_backwards; if (p_animation) { set_animation(p_animation); - if (backwards && get_frame() == 0) + if (frames.is_valid() && backwards && get_frame() == 0) { set_frame(frames->get_frame_count(p_animation) - 1); + } } _set_playing(true); } void AnimatedSprite2D::stop() { - _set_playing(false); } 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; } @@ -642,62 +412,44 @@ float AnimatedSprite2D::_get_frame_duration() { } void AnimatedSprite2D::_reset_timeout() { - - if (!playing) + if (!playing) { return; + } timeout = _get_frame_duration(); is_over = false; } void AnimatedSprite2D::set_animation(const StringName &p_animation) { - ERR_FAIL_COND_MSG(frames == nullptr, vformat("There is no animation with name '%s'.", p_animation)); ERR_FAIL_COND_MSG(frames->get_animation_names().find(p_animation) == -1, vformat("There is no animation with name '%s'.", p_animation)); - if (animation == p_animation) + if (animation == p_animation) { return; + } animation = p_animation; _reset_timeout(); set_frame(0); - _change_notify(); + notify_property_list_changed(); update(); } -StringName AnimatedSprite2D::get_animation() const { +StringName AnimatedSprite2D::get_animation() const { return animation; } -String AnimatedSprite2D::get_configuration_warning() const { +TypedArray<String> AnimatedSprite2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); if (frames.is_null()) { - return TTR("A SpriteFrames resource must be created or set in the \"Frames\" property in order for AnimatedSprite to display frames."); + warnings.push_back(TTR("A SpriteFrames resource must be created or set in the \"Frames\" property in order for AnimatedSprite to display frames.")); } - return String(); -} - -void AnimatedSprite2D::set_specular_color(const Color &p_color) { - specular_color = p_color; - update(); -} - -Color AnimatedSprite2D::get_specular_color() const { - return specular_color; -} - -void AnimatedSprite2D::set_shininess(float p_shininess) { - shininess = CLAMP(p_shininess, 0.0, 1.0); - update(); -} - -float AnimatedSprite2D::get_shininess() const { - return shininess; + return warnings; } void AnimatedSprite2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_sprite_frames", "sprite_frames"), &AnimatedSprite2D::set_sprite_frames); ClassDB::bind_method(D_METHOD("get_sprite_frames"), &AnimatedSprite2D::get_sprite_frames); @@ -729,12 +481,6 @@ void AnimatedSprite2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_speed_scale", "speed_scale"), &AnimatedSprite2D::set_speed_scale); ClassDB::bind_method(D_METHOD("get_speed_scale"), &AnimatedSprite2D::get_speed_scale); - ClassDB::bind_method(D_METHOD("set_specular_color", "color"), &AnimatedSprite2D::set_specular_color); - ClassDB::bind_method(D_METHOD("get_specular_color"), &AnimatedSprite2D::get_specular_color); - - ClassDB::bind_method(D_METHOD("set_shininess", "shininess"), &AnimatedSprite2D::set_shininess); - ClassDB::bind_method(D_METHOD("get_shininess"), &AnimatedSprite2D::get_shininess); - ADD_SIGNAL(MethodInfo("frame_changed")); ADD_SIGNAL(MethodInfo("animation_finished")); @@ -744,9 +490,6 @@ void AnimatedSprite2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "frame"), "set_frame", "get_frame"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale"), "set_speed_scale", "get_speed_scale"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing"), "_set_playing", "_is_playing"); - ADD_GROUP("Lighting", ""); - ADD_PROPERTY(PropertyInfo(Variant::COLOR, "specular_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_specular_color", "get_specular_color"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "shininess", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_shininess", "get_shininess"); ADD_GROUP("Offset", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "centered"), "set_centered", "is_centered"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_offset", "get_offset"); @@ -755,18 +498,4 @@ void AnimatedSprite2D::_bind_methods() { } AnimatedSprite2D::AnimatedSprite2D() { - - centered = true; - hflip = false; - vflip = false; - - frame = 0; - speed_scale = 1.0f; - playing = false; - backwards = false; - animation = "default"; - timeout = 0; - is_over = false; - specular_color = Color(1, 1, 1, 1); - shininess = 1.0; } diff --git a/scene/2d/animated_sprite_2d.h b/scene/2d/animated_sprite_2d.h index 726ecefd32..ac4b20a6d9 100644 --- a/scene/2d/animated_sprite_2d.h +++ b/scene/2d/animated_sprite_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,163 +32,53 @@ #define ANIMATED_SPRITE_2D_H #include "scene/2d/node_2d.h" -#include "scene/resources/texture.h" - -class SpriteFrames : public Resource { - - GDCLASS(SpriteFrames, Resource); - - struct Anim { - - float speed; - bool loop; - Vector<Ref<Texture2D>> frames; - - Anim() { - loop = true; - speed = 5; - } - - StringName normal_name; - StringName specular_name; - }; - - Color specular_color; - float shininess; - - Map<StringName, Anim> animations; - - Array _get_frames() const; - void _set_frames(const Array &p_frames); - - Array _get_animations() const; - void _set_animations(const Array &p_animations); - - Vector<String> _get_animation_list() const; - -protected: - static void _bind_methods(); - -public: - void add_animation(const StringName &p_anim); - bool has_animation(const StringName &p_anim) const; - void remove_animation(const StringName &p_anim); - void rename_animation(const StringName &p_prev, const StringName &p_next); - - void get_animation_list(List<StringName> *r_animations) const; - Vector<String> get_animation_names() const; - - void set_animation_speed(const StringName &p_anim, float p_fps); - float get_animation_speed(const StringName &p_anim) const; - - void set_animation_loop(const StringName &p_anim, bool p_loop); - bool get_animation_loop(const StringName &p_anim) const; - - void add_frame(const StringName &p_anim, const Ref<Texture2D> &p_frame, int p_at_pos = -1); - int get_frame_count(const StringName &p_anim) const; - _FORCE_INLINE_ Ref<Texture2D> get_frame(const StringName &p_anim, int p_idx) const { - - const Map<StringName, Anim>::Element *E = animations.find(p_anim); - ERR_FAIL_COND_V_MSG(!E, Ref<Texture2D>(), "Animation '" + String(p_anim) + "' doesn't exist."); - ERR_FAIL_COND_V(p_idx < 0, Ref<Texture2D>()); - if (p_idx >= E->get().frames.size()) - return Ref<Texture2D>(); - - return E->get().frames[p_idx]; - } - - _FORCE_INLINE_ Ref<Texture2D> get_normal_frame(const StringName &p_anim, int p_idx) const { - - const Map<StringName, Anim>::Element *E = animations.find(p_anim); - ERR_FAIL_COND_V_MSG(!E, Ref<Texture2D>(), "Animation '" + String(p_anim) + "' doesn't exist."); - ERR_FAIL_COND_V(p_idx < 0, Ref<Texture2D>()); - - const Map<StringName, Anim>::Element *EN = animations.find(E->get().normal_name); - - if (!EN || p_idx >= EN->get().frames.size()) - return Ref<Texture2D>(); - - return EN->get().frames[p_idx]; - } - - _FORCE_INLINE_ Ref<Texture2D> get_specular_frame(const StringName &p_anim, int p_idx) const { - - const Map<StringName, Anim>::Element *E = animations.find(p_anim); - ERR_FAIL_COND_V(!E, Ref<Texture2D>()); - ERR_FAIL_COND_V(p_idx < 0, Ref<Texture2D>()); - - const Map<StringName, Anim>::Element *EN = animations.find(E->get().specular_name); - - if (!EN || p_idx >= EN->get().frames.size()) - return Ref<Texture2D>(); - - return EN->get().frames[p_idx]; - } - - void set_frame(const StringName &p_anim, int p_idx, const Ref<Texture2D> &p_frame) { - Map<StringName, Anim>::Element *E = animations.find(p_anim); - ERR_FAIL_COND_MSG(!E, "Animation '" + String(p_anim) + "' doesn't exist."); - ERR_FAIL_COND(p_idx < 0); - if (p_idx >= E->get().frames.size()) - return; - E->get().frames.write[p_idx] = p_frame; - } - void remove_frame(const StringName &p_anim, int p_idx); - void clear(const StringName &p_anim); - void clear_all(); - - SpriteFrames(); -}; +#include "scene/resources/sprite_frames.h" class AnimatedSprite2D : public Node2D { - GDCLASS(AnimatedSprite2D, Node2D); Ref<SpriteFrames> frames; - bool playing; - bool backwards; - StringName animation; - int frame; - float speed_scale; + bool playing = false; + bool backwards = false; + StringName animation = "default"; + int frame = 0; + float speed_scale = 1.0f; - bool centered; + bool centered = true; Point2 offset; - bool is_over; - float timeout; + bool is_over = false; + float timeout = 0.0; - bool hflip; - bool vflip; + bool hflip = false; + bool vflip = false; void _res_changed(); - float _get_frame_duration(); + double _get_frame_duration(); void _reset_timeout(); void _set_playing(bool p_playing); bool _is_playing() const; Rect2 _get_rect() const; - Color specular_color; - float shininess; - protected: static void _bind_methods(); void _notification(int p_what); - virtual void _validate_property(PropertyInfo &property) const; + virtual void _validate_property(PropertyInfo &property) const override; public: #ifdef TOOLS_ENABLED - virtual Dictionary _edit_get_state() const; - virtual void _edit_set_state(const Dictionary &p_state); - - virtual void _edit_set_pivot(const Point2 &p_pivot); - virtual Point2 _edit_get_pivot() const; - virtual bool _edit_use_pivot() const; - virtual Rect2 _edit_get_rect() const; - virtual bool _edit_use_rect() const; + virtual Dictionary _edit_get_state() const override; + virtual void _edit_set_state(const Dictionary &p_state) override; + + virtual void _edit_set_pivot(const Point2 &p_pivot) override; + virtual Point2 _edit_get_pivot() const override; + virtual bool _edit_use_pivot() const override; + virtual Rect2 _edit_get_rect() const override; + virtual bool _edit_use_rect() const override; #endif - virtual Rect2 get_anchorable_rect() const; + virtual Rect2 get_anchorable_rect() const override; void set_sprite_frames(const Ref<SpriteFrames> &p_frames); Ref<SpriteFrames> get_sprite_frames() const; @@ -203,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; @@ -218,13 +108,7 @@ public: void set_flip_v(bool p_flip); bool is_flipped_v() const; - void set_specular_color(const Color &p_color); - Color get_specular_color() const; - - void set_shininess(float p_shininess); - float get_shininess() const; - - virtual String get_configuration_warning() const; + TypedArray<String> get_configuration_warnings() const override; AnimatedSprite2D(); }; diff --git a/scene/2d/area_2d.cpp b/scene/2d/area_2d.cpp index c46b6eeb5c..d78b9847e6 100644 --- a/scene/2d/area_2d.cpp +++ b/scene/2d/area_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,30 +32,26 @@ #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; PhysicsServer2D::get_singleton()->area_set_space_override_mode(get_rid(), PhysicsServer2D::AreaSpaceOverrideMode(p_mode)); } -Area2D::SpaceOverride Area2D::get_space_override_mode() const { +Area2D::SpaceOverride Area2D::get_space_override_mode() const { return space_override; } void Area2D::set_gravity_is_point(bool p_enabled) { - gravity_is_point = p_enabled; PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_GRAVITY_IS_POINT, p_enabled); } -bool Area2D::is_gravity_a_point() const { +bool Area2D::is_gravity_a_point() const { return gravity_is_point; } void Area2D::set_gravity_distance_scale(real_t p_scale) { - gravity_distance_scale = p_scale; PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_GRAVITY_DISTANCE_SCALE, p_scale); } @@ -65,58 +61,51 @@ real_t Area2D::get_gravity_distance_scale() const { } void Area2D::set_gravity_vector(const Vector2 &p_vec) { - gravity_vec = p_vec; PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_GRAVITY_VECTOR, p_vec); } -Vector2 Area2D::get_gravity_vector() const { +Vector2 Area2D::get_gravity_vector() const { return gravity_vec; } void Area2D::set_gravity(real_t p_gravity) { - gravity = p_gravity; PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_GRAVITY, p_gravity); } -real_t Area2D::get_gravity() const { +real_t Area2D::get_gravity() const { return gravity; } void Area2D::set_linear_damp(real_t p_linear_damp) { - linear_damp = p_linear_damp; PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_LINEAR_DAMP, p_linear_damp); } -real_t Area2D::get_linear_damp() const { +real_t Area2D::get_linear_damp() const { return linear_damp; } void Area2D::set_angular_damp(real_t p_angular_damp) { - angular_damp = p_angular_damp; PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_ANGULAR_DAMP, p_angular_damp); } real_t Area2D::get_angular_damp() const { - return angular_damp; } void Area2D::set_priority(real_t p_priority) { - priority = p_priority; PhysicsServer2D::get_singleton()->area_set_param(get_rid(), PhysicsServer2D::AREA_PARAM_PRIORITY, p_priority); } -real_t Area2D::get_priority() const { +real_t Area2D::get_priority() const { return priority; } void Area2D::_body_enter_tree(ObjectID p_id) { - Object *obj = ObjectDB::get_instance(p_id); Node *node = Object::cast_to<Node>(obj); ERR_FAIL_COND(!node); @@ -128,13 +117,11 @@ void Area2D::_body_enter_tree(ObjectID p_id) { E->get().in_tree = true; emit_signal(SceneStringNames::get_singleton()->body_entered, node); for (int i = 0; i < E->get().shapes.size(); i++) { - - emit_signal(SceneStringNames::get_singleton()->body_shape_entered, p_id, node, E->get().shapes[i].body_shape, E->get().shapes[i].area_shape); + emit_signal(SceneStringNames::get_singleton()->body_shape_entered, E->get().rid, node, E->get().shapes[i].body_shape, E->get().shapes[i].area_shape); } } void Area2D::_body_exit_tree(ObjectID p_id) { - Object *obj = ObjectDB::get_instance(p_id); Node *node = Object::cast_to<Node>(obj); ERR_FAIL_COND(!node); @@ -144,13 +131,11 @@ void Area2D::_body_exit_tree(ObjectID p_id) { E->get().in_tree = false; emit_signal(SceneStringNames::get_singleton()->body_exited, node); for (int i = 0; i < E->get().shapes.size(); i++) { - - emit_signal(SceneStringNames::get_singleton()->body_shape_exited, p_id, node, E->get().shapes[i].body_shape, E->get().shapes[i].area_shape); + emit_signal(SceneStringNames::get_singleton()->body_shape_exited, E->get().rid, node, E->get().shapes[i].body_shape, E->get().shapes[i].area_shape); } } void Area2D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, int p_body_shape, int p_area_shape) { - bool body_in = p_status == PhysicsServer2D::AREA_BODY_ADDED; ObjectID objid = p_instance; @@ -167,8 +152,8 @@ void Area2D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i if (body_in) { if (!E) { - E = body_map.insert(objid, BodyState()); + E->get().rid = p_body; E->get().rc = 0; E->get().in_tree = node && node->is_inside_tree(); if (node) { @@ -180,46 +165,41 @@ void Area2D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i } } E->get().rc++; - if (node) + if (node) { E->get().shapes.insert(ShapePair(p_body_shape, p_area_shape)); + } if (!node || E->get().in_tree) { - emit_signal(SceneStringNames::get_singleton()->body_shape_entered, objid, node, p_body_shape, p_area_shape); + emit_signal(SceneStringNames::get_singleton()->body_shape_entered, p_body, node, p_body_shape, p_area_shape); } } else { - E->get().rc--; - if (node) + if (node) { E->get().shapes.erase(ShapePair(p_body_shape, p_area_shape)); + } - bool eraseit = false; - + bool in_tree = E->get().in_tree; if (E->get().rc == 0) { - + body_map.erase(E); if (node) { node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area2D::_body_enter_tree)); node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area2D::_body_exit_tree)); - if (E->get().in_tree) + if (in_tree) { emit_signal(SceneStringNames::get_singleton()->body_exited, obj); + } } - - eraseit = true; } - if (!node || E->get().in_tree) { - emit_signal(SceneStringNames::get_singleton()->body_shape_exited, objid, obj, p_body_shape, p_area_shape); + if (!node || in_tree) { + emit_signal(SceneStringNames::get_singleton()->body_shape_exited, p_body, obj, p_body_shape, p_area_shape); } - - if (eraseit) - body_map.erase(E); } locked = false; } void Area2D::_area_enter_tree(ObjectID p_id) { - Object *obj = ObjectDB::get_instance(p_id); Node *node = Object::cast_to<Node>(obj); ERR_FAIL_COND(!node); @@ -231,13 +211,11 @@ void Area2D::_area_enter_tree(ObjectID p_id) { E->get().in_tree = true; emit_signal(SceneStringNames::get_singleton()->area_entered, node); for (int i = 0; i < E->get().shapes.size(); i++) { - - emit_signal(SceneStringNames::get_singleton()->area_shape_entered, p_id, node, E->get().shapes[i].area_shape, E->get().shapes[i].self_shape); + emit_signal(SceneStringNames::get_singleton()->area_shape_entered, E->get().rid, node, E->get().shapes[i].area_shape, E->get().shapes[i].self_shape); } } void Area2D::_area_exit_tree(ObjectID p_id) { - Object *obj = ObjectDB::get_instance(p_id); Node *node = Object::cast_to<Node>(obj); ERR_FAIL_COND(!node); @@ -247,13 +225,11 @@ void Area2D::_area_exit_tree(ObjectID p_id) { E->get().in_tree = false; emit_signal(SceneStringNames::get_singleton()->area_exited, node); for (int i = 0; i < E->get().shapes.size(); i++) { - - emit_signal(SceneStringNames::get_singleton()->area_shape_exited, p_id, node, E->get().shapes[i].area_shape, E->get().shapes[i].self_shape); + emit_signal(SceneStringNames::get_singleton()->area_shape_exited, E->get().rid, node, E->get().shapes[i].area_shape, E->get().shapes[i].self_shape); } } void Area2D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, int p_area_shape, int p_self_shape) { - bool area_in = p_status == PhysicsServer2D::AREA_BODY_ADDED; ObjectID objid = p_instance; @@ -269,8 +245,8 @@ void Area2D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, i if (area_in) { if (!E) { - E = area_map.insert(objid, AreaState()); + E->get().rid = p_area; E->get().rc = 0; E->get().in_tree = node && node->is_inside_tree(); if (node) { @@ -282,46 +258,41 @@ void Area2D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, i } } E->get().rc++; - if (node) + if (node) { E->get().shapes.insert(AreaShapePair(p_area_shape, p_self_shape)); + } if (!node || E->get().in_tree) { - emit_signal(SceneStringNames::get_singleton()->area_shape_entered, objid, node, p_area_shape, p_self_shape); + emit_signal(SceneStringNames::get_singleton()->area_shape_entered, p_area, node, p_area_shape, p_self_shape); } } else { - E->get().rc--; - if (node) + if (node) { E->get().shapes.erase(AreaShapePair(p_area_shape, p_self_shape)); + } - bool eraseit = false; - + bool in_tree = E->get().in_tree; if (E->get().rc == 0) { - + area_map.erase(E); if (node) { node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area2D::_area_enter_tree)); node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area2D::_area_exit_tree)); - if (E->get().in_tree) + if (in_tree) { emit_signal(SceneStringNames::get_singleton()->area_exited, obj); + } } - - eraseit = true; } - if (!node || E->get().in_tree) { - emit_signal(SceneStringNames::get_singleton()->area_shape_exited, objid, obj, p_area_shape, p_self_shape); + if (!node || in_tree) { + emit_signal(SceneStringNames::get_singleton()->area_shape_exited, p_area, obj, p_area_shape, p_self_shape); } - - if (eraseit) - area_map.erase(E); } locked = false; } void Area2D::_clear_monitoring() { - ERR_FAIL_COND_MSG(locked, "This function can't be used during the in/out signal."); { @@ -330,22 +301,22 @@ void Area2D::_clear_monitoring() { //disconnect all monitored stuff for (Map<ObjectID, BodyState>::Element *E = bmcopy.front(); E; E = E->next()) { - Object *obj = ObjectDB::get_instance(E->key()); Node *node = Object::cast_to<Node>(obj); - if (!node) //node may have been deleted in previous frame or at other legitimate point + if (!node) { //node may have been deleted in previous frame or at other legitimate point continue; + } node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area2D::_body_enter_tree)); node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area2D::_body_exit_tree)); - if (!E->get().in_tree) + if (!E->get().in_tree) { continue; + } for (int i = 0; i < E->get().shapes.size(); i++) { - - emit_signal(SceneStringNames::get_singleton()->body_shape_exited, E->key(), node, E->get().shapes[i].body_shape, E->get().shapes[i].area_shape); + emit_signal(SceneStringNames::get_singleton()->body_shape_exited, E->get().rid, node, E->get().shapes[i].body_shape, E->get().shapes[i].area_shape); } emit_signal(SceneStringNames::get_singleton()->body_exited, obj); @@ -353,28 +324,27 @@ void Area2D::_clear_monitoring() { } { - Map<ObjectID, AreaState> bmcopy = area_map; area_map.clear(); //disconnect all monitored stuff for (Map<ObjectID, AreaState>::Element *E = bmcopy.front(); E; E = E->next()) { - Object *obj = ObjectDB::get_instance(E->key()); Node *node = Object::cast_to<Node>(obj); - if (!node) //node may have been deleted in previous frame or at other legitimate point + if (!node) { //node may have been deleted in previous frame or at other legitimate point continue; + } node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area2D::_area_enter_tree)); node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area2D::_area_exit_tree)); - if (!E->get().in_tree) + if (!E->get().in_tree) { continue; + } for (int i = 0; i < E->get().shapes.size(); i++) { - - emit_signal(SceneStringNames::get_singleton()->area_shape_exited, E->key(), node, E->get().shapes[i].area_shape, E->get().shapes[i].self_shape); + emit_signal(SceneStringNames::get_singleton()->area_shape_exited, E->get().rid, node, E->get().shapes[i].area_shape, E->get().shapes[i].self_shape); } emit_signal(SceneStringNames::get_singleton()->area_exited, obj); @@ -383,26 +353,22 @@ void Area2D::_clear_monitoring() { } void Area2D::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_EXIT_TREE: { - _clear_monitoring(); } break; } } void Area2D::set_monitoring(bool p_enable) { - - if (p_enable == monitoring) + if (p_enable == monitoring) { return; + } ERR_FAIL_COND_MSG(locked, "Function blocked during in/out signal. Use set_deferred(\"monitoring\", true/false)."); monitoring = p_enable; if (monitoring) { - PhysicsServer2D::get_singleton()->area_set_monitor_callback(get_rid(), this, SceneStringNames::get_singleton()->_body_inout); PhysicsServer2D::get_singleton()->area_set_area_monitor_callback(get_rid(), this, SceneStringNames::get_singleton()->_area_inout); @@ -414,16 +380,15 @@ void Area2D::set_monitoring(bool p_enable) { } bool Area2D::is_monitoring() const { - return monitoring; } void Area2D::set_monitorable(bool p_enable) { - ERR_FAIL_COND_MSG(locked || (is_inside_tree() && PhysicsServer2D::get_singleton()->is_flushing_queries()), "Function blocked during in/out signal. Use set_deferred(\"monitorable\", true/false)."); - if (p_enable == monitorable) + if (p_enable == monitorable) { return; + } monitorable = p_enable; @@ -431,14 +396,12 @@ void Area2D::set_monitorable(bool p_enable) { } bool Area2D::is_monitorable() const { - return monitorable; } -Array Area2D::get_overlapping_bodies() const { - +TypedArray<Node2D> Area2D::get_overlapping_bodies() const { ERR_FAIL_COND_V_MSG(!monitoring, Array(), "Can't find overlapping bodies when monitoring is off."); - Array ret; + TypedArray<Node2D> ret; ret.resize(body_map.size()); int idx = 0; for (const Map<ObjectID, BodyState>::Element *E = body_map.front(); E; E = E->next()) { @@ -453,10 +416,9 @@ Array Area2D::get_overlapping_bodies() const { return ret; } -Array Area2D::get_overlapping_areas() const { - +TypedArray<Area2D> Area2D::get_overlapping_areas() const { ERR_FAIL_COND_V_MSG(!monitoring, Array(), "Can't find overlapping bodies when monitoring is off."); - Array ret; + TypedArray<Area2D> ret; ret.resize(area_map.size()); int idx = 0; for (const Map<ObjectID, AreaState>::Element *E = area_map.front(); E; E = E->next()) { @@ -472,92 +434,36 @@ Array Area2D::get_overlapping_areas() const { } bool Area2D::overlaps_area(Node *p_area) const { - ERR_FAIL_NULL_V(p_area, false); const Map<ObjectID, AreaState>::Element *E = area_map.find(p_area->get_instance_id()); - if (!E) + if (!E) { return false; + } return E->get().in_tree; } bool Area2D::overlaps_body(Node *p_body) const { - ERR_FAIL_NULL_V(p_body, false); const Map<ObjectID, BodyState>::Element *E = body_map.find(p_body->get_instance_id()); - if (!E) + if (!E) { return false; + } return E->get().in_tree; } -void Area2D::set_collision_mask(uint32_t p_mask) { - - collision_mask = p_mask; - PhysicsServer2D::get_singleton()->area_set_collision_mask(get_rid(), p_mask); -} - -uint32_t Area2D::get_collision_mask() const { - - return collision_mask; -} - -void Area2D::set_collision_layer(uint32_t p_layer) { - - collision_layer = p_layer; - PhysicsServer2D::get_singleton()->area_set_collision_layer(get_rid(), p_layer); -} - -uint32_t Area2D::get_collision_layer() const { - - return collision_layer; -} - -void Area2D::set_collision_mask_bit(int p_bit, bool p_value) { - - uint32_t mask = get_collision_mask(); - if (p_value) - mask |= 1 << p_bit; - else - mask &= ~(1 << p_bit); - set_collision_mask(mask); -} - -bool Area2D::get_collision_mask_bit(int p_bit) const { - - return get_collision_mask() & (1 << p_bit); -} - -void Area2D::set_collision_layer_bit(int p_bit, bool p_value) { - - uint32_t layer = get_collision_layer(); - if (p_value) - layer |= 1 << p_bit; - else - layer &= ~(1 << p_bit); - set_collision_layer(layer); -} - -bool Area2D::get_collision_layer_bit(int p_bit) const { - - return get_collision_layer() & (1 << p_bit); -} - void Area2D::set_audio_bus_override(bool p_override) { - audio_bus_override = p_override; } bool Area2D::is_overriding_audio_bus() const { - return audio_bus_override; } void Area2D::set_audio_bus_name(const StringName &p_audio_bus) { - audio_bus = p_audio_bus; } StringName Area2D::get_audio_bus_name() const { - for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) { if (AudioServer::get_singleton()->get_bus_name(i) == audio_bus) { return audio_bus; @@ -567,13 +473,12 @@ StringName Area2D::get_audio_bus_name() const { } void Area2D::_validate_property(PropertyInfo &property) const { - if (property.name == "audio_bus_name") { - String options; for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) { - if (i > 0) + if (i > 0) { options += ","; + } String name = AudioServer::get_singleton()->get_bus_name(i); options += name; } @@ -607,18 +512,6 @@ void Area2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_priority", "priority"), &Area2D::set_priority); ClassDB::bind_method(D_METHOD("get_priority"), &Area2D::get_priority); - ClassDB::bind_method(D_METHOD("set_collision_mask", "collision_mask"), &Area2D::set_collision_mask); - ClassDB::bind_method(D_METHOD("get_collision_mask"), &Area2D::get_collision_mask); - - ClassDB::bind_method(D_METHOD("set_collision_layer", "collision_layer"), &Area2D::set_collision_layer); - ClassDB::bind_method(D_METHOD("get_collision_layer"), &Area2D::get_collision_layer); - - ClassDB::bind_method(D_METHOD("set_collision_mask_bit", "bit", "value"), &Area2D::set_collision_mask_bit); - ClassDB::bind_method(D_METHOD("get_collision_mask_bit", "bit"), &Area2D::get_collision_mask_bit); - - ClassDB::bind_method(D_METHOD("set_collision_layer_bit", "bit", "value"), &Area2D::set_collision_layer_bit); - ClassDB::bind_method(D_METHOD("get_collision_layer_bit", "bit"), &Area2D::get_collision_layer_bit); - ClassDB::bind_method(D_METHOD("set_monitoring", "enable"), &Area2D::set_monitoring); ClassDB::bind_method(D_METHOD("is_monitoring"), &Area2D::is_monitoring); @@ -640,29 +533,28 @@ void Area2D::_bind_methods() { ClassDB::bind_method(D_METHOD("_body_inout"), &Area2D::_body_inout); ClassDB::bind_method(D_METHOD("_area_inout"), &Area2D::_area_inout); - ADD_SIGNAL(MethodInfo("body_shape_entered", PropertyInfo(Variant::INT, "body_id"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::INT, "body_shape"), PropertyInfo(Variant::INT, "area_shape"))); - ADD_SIGNAL(MethodInfo("body_shape_exited", PropertyInfo(Variant::INT, "body_id"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::INT, "body_shape"), PropertyInfo(Variant::INT, "area_shape"))); - ADD_SIGNAL(MethodInfo("body_entered", PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); - ADD_SIGNAL(MethodInfo("body_exited", PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); + ADD_SIGNAL(MethodInfo("body_shape_entered", PropertyInfo(Variant::RID, "body_rid"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node2D"), PropertyInfo(Variant::INT, "body_shape"), PropertyInfo(Variant::INT, "local_shape"))); + ADD_SIGNAL(MethodInfo("body_shape_exited", PropertyInfo(Variant::RID, "body_rid"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node2D"), PropertyInfo(Variant::INT, "body_shape"), PropertyInfo(Variant::INT, "local_shape"))); + ADD_SIGNAL(MethodInfo("body_entered", PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node2D"))); + ADD_SIGNAL(MethodInfo("body_exited", PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node2D"))); - ADD_SIGNAL(MethodInfo("area_shape_entered", PropertyInfo(Variant::INT, "area_id"), PropertyInfo(Variant::OBJECT, "area", PROPERTY_HINT_RESOURCE_TYPE, "Area2D"), PropertyInfo(Variant::INT, "area_shape"), PropertyInfo(Variant::INT, "self_shape"))); - ADD_SIGNAL(MethodInfo("area_shape_exited", PropertyInfo(Variant::INT, "area_id"), PropertyInfo(Variant::OBJECT, "area", PROPERTY_HINT_RESOURCE_TYPE, "Area2D"), PropertyInfo(Variant::INT, "area_shape"), PropertyInfo(Variant::INT, "self_shape"))); + ADD_SIGNAL(MethodInfo("area_shape_entered", PropertyInfo(Variant::RID, "area_rid"), PropertyInfo(Variant::OBJECT, "area", PROPERTY_HINT_RESOURCE_TYPE, "Area2D"), PropertyInfo(Variant::INT, "area_shape"), PropertyInfo(Variant::INT, "local_shape"))); + ADD_SIGNAL(MethodInfo("area_shape_exited", PropertyInfo(Variant::RID, "area_rid"), PropertyInfo(Variant::OBJECT, "area", PROPERTY_HINT_RESOURCE_TYPE, "Area2D"), PropertyInfo(Variant::INT, "area_shape"), PropertyInfo(Variant::INT, "local_shape"))); ADD_SIGNAL(MethodInfo("area_entered", PropertyInfo(Variant::OBJECT, "area", PROPERTY_HINT_RESOURCE_TYPE, "Area2D"))); ADD_SIGNAL(MethodInfo("area_exited", PropertyInfo(Variant::OBJECT, "area", PROPERTY_HINT_RESOURCE_TYPE, "Area2D"))); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "monitoring"), "set_monitoring", "is_monitoring"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "monitorable"), "set_monitorable", "is_monitorable"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,128,1"), "set_priority", "get_priority"); + + ADD_GROUP("Physics Overrides", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "space_override", PROPERTY_HINT_ENUM, "Disabled,Combine,Combine-Replace,Replace,Replace-Combine"), "set_space_override_mode", "get_space_override_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gravity_point"), "set_gravity_is_point", "is_gravity_a_point"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity_distance_scale", PROPERTY_HINT_EXP_RANGE, "0,1024,0.001,or_greater"), "set_gravity_distance_scale", "get_gravity_distance_scale"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity_distance_scale", PROPERTY_HINT_RANGE, "0,1024,0.001,or_greater,exp"), "set_gravity_distance_scale", "get_gravity_distance_scale"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "gravity_vec"), "set_gravity_vector", "get_gravity_vector"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity", PROPERTY_HINT_RANGE, "-1024,1024,0.001"), "set_gravity", "get_gravity"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gravity", PROPERTY_HINT_RANGE, "-4096,4096,0.001,or_lesser,or_greater"), "set_gravity", "get_gravity"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "linear_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_linear_damp", "get_linear_damp"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "angular_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_angular_damp", "get_angular_damp"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,128,1"), "set_priority", "get_priority"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "monitoring"), "set_monitoring", "is_monitoring"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "monitorable"), "set_monitorable", "is_monitorable"); - ADD_GROUP("Collision", "collision_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_layer", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_layer", "get_collision_layer"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_mask", "get_collision_mask"); ADD_GROUP("Audio Bus", "audio_bus_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "audio_bus_override"), "set_audio_bus_override", "is_overriding_audio_bus"); @@ -677,21 +569,8 @@ void Area2D::_bind_methods() { Area2D::Area2D() : CollisionObject2D(PhysicsServer2D::get_singleton()->area_create(), true) { - - space_override = SPACE_OVERRIDE_DISABLED; - set_gravity(98); + set_gravity(980); set_gravity_vector(Vector2(0, 1)); - gravity_is_point = false; - gravity_distance_scale = 0; - linear_damp = 0.1; - angular_damp = 1; - locked = false; - priority = 0; - monitoring = false; - monitorable = false; - collision_mask = 1; - collision_layer = 1; - audio_bus_override = false; set_monitoring(true); set_monitorable(true); } diff --git a/scene/2d/area_2d.h b/scene/2d/area_2d.h index c5e6635412..2c29e4660d 100644 --- a/scene/2d/area_2d.h +++ b/scene/2d/area_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,11 +31,10 @@ #ifndef AREA_2D_H #define AREA_2D_H -#include "core/vset.h" +#include "core/templates/vset.h" #include "scene/2d/collision_object_2d.h" class Area2D : public CollisionObject2D { - GDCLASS(Area2D, CollisionObject2D); public: @@ -48,19 +47,17 @@ public: }; private: - SpaceOverride space_override; + SpaceOverride space_override = SPACE_OVERRIDE_DISABLED; Vector2 gravity_vec; real_t gravity; - bool gravity_is_point; - real_t gravity_distance_scale; - real_t linear_damp; - real_t angular_damp; - uint32_t collision_mask; - uint32_t collision_layer; - int priority; - bool monitoring; - bool monitorable; - bool locked; + bool gravity_is_point = false; + real_t gravity_distance_scale = 0.0; + real_t linear_damp = 0.1; + real_t angular_damp = 1.0; + int priority = 0; + bool monitoring = false; + bool monitorable = false; + bool locked = false; void _body_inout(int p_status, const RID &p_body, ObjectID p_instance, int p_body_shape, int p_area_shape); @@ -68,14 +65,14 @@ private: void _body_exit_tree(ObjectID p_id); struct ShapePair { - - int body_shape; - int area_shape; + int body_shape = 0; + int area_shape = 0; bool operator<(const ShapePair &p_sp) const { - if (body_shape == p_sp.body_shape) + if (body_shape == p_sp.body_shape) { return area_shape < p_sp.area_shape; - else + } else { return body_shape < p_sp.body_shape; + } } ShapePair() {} @@ -86,9 +83,9 @@ private: }; struct BodyState { - - int rc; - bool in_tree; + RID rid; + int rc = 0; + bool in_tree = false; VSet<ShapePair> shapes; }; @@ -100,14 +97,14 @@ private: void _area_exit_tree(ObjectID p_id); struct AreaShapePair { - - int area_shape; - int self_shape; + int area_shape = 0; + int self_shape = 0; bool operator<(const AreaShapePair &p_sp) const { - if (area_shape == p_sp.area_shape) + if (area_shape == p_sp.area_shape) { return self_shape < p_sp.self_shape; - else + } else { return area_shape < p_sp.area_shape; + } } AreaShapePair() {} @@ -118,22 +115,22 @@ private: }; struct AreaState { - - int rc; - bool in_tree; + RID rid; + int rc = 0; + bool in_tree = false; VSet<AreaShapePair> shapes; }; Map<ObjectID, AreaState> area_map; void _clear_monitoring(); - bool audio_bus_override; + bool audio_bus_override = false; StringName audio_bus; protected: void _notification(int p_what); static void _bind_methods(); - void _validate_property(PropertyInfo &property) const; + void _validate_property(PropertyInfo &property) const override; public: void set_space_override_mode(SpaceOverride p_mode); @@ -166,20 +163,8 @@ public: void set_monitorable(bool p_enable); bool is_monitorable() const; - void set_collision_mask(uint32_t p_mask); - uint32_t get_collision_mask() const; - - void set_collision_layer(uint32_t p_layer); - uint32_t get_collision_layer() const; - - void set_collision_mask_bit(int p_bit, bool p_value); - bool get_collision_mask_bit(int p_bit) const; - - void set_collision_layer_bit(int p_bit, bool p_value); - bool get_collision_layer_bit(int p_bit) const; - - Array get_overlapping_bodies() const; //function for script - Array get_overlapping_areas() const; //function for script + TypedArray<Node2D> get_overlapping_bodies() const; //function for script + TypedArray<Area2D> get_overlapping_areas() const; //function for script bool overlaps_area(Node *p_area) const; bool overlaps_body(Node *p_body) const; diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp index 55d111439a..8401909384 100644 --- a/scene/2d/audio_stream_player_2d.cpp +++ b/scene/2d/audio_stream_player_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,133 +30,21 @@ #include "audio_stream_player_2d.h" -#include "core/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 || - (stream_paused && !stream_paused_fade_out)) { - return; - } - - if (setseek >= 0.0) { - stream_playback->start(setseek); - setseek = -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 - for (int i = 0; i < output_count; 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 = output_count; - - //stream is no longer active, disable this. - if (!stream_playback->is_playing()) { - active = false; - } - - output_ready = false; - 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) { @@ -171,245 +59,258 @@ 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) { - List<Viewport *> viewports; - 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 - - PhysicsDirectSpaceState2D *space_state = PhysicsServer2D::get_singleton()->space_get_direct_state(world_2d->get_space()); - - PhysicsDirectSpaceState2D::ShapeResult sr[MAX_INTERSECT_AREAS]; - - int areas = space_state->intersect_point(global_pos, sr, MAX_INTERSECT_AREAS, Set<RID>(), area_mask, false, true); - - for (int i = 0; i < areas; i++) { + 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); + } - Area2D *area2d = Object::cast_to<Area2D>(sr[i].collider); - if (!area2d) - continue; + 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); + } + } - if (!area2d->is_overriding_audio_bus()) - continue; + while (stream_playbacks.size() > max_polyphony) { + AudioServer::get_singleton()->stop_playback_stream(stream_playbacks[0]); + stream_playbacks.remove(0); + } + } +} - StringName bus_name = area2d->get_audio_bus_name(); - bus_index = AudioServer::get_singleton()->thread_find_bus_index(bus_name); - break; - } +StringName AudioStreamPlayer2D::_get_actual_bus() { + Vector2 global_pos = get_global_position(); - world_2d->get_viewport_list(&viewports); - for (List<Viewport *>::Element *E = viewports.front(); E; E = E->next()) { + //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")); - Viewport *vp = E->get(); - if (vp->is_audio_listener_2d()) { + PhysicsDirectSpaceState2D *space_state = PhysicsServer2D::get_singleton()->space_get_direct_state(world_2d->get_space()); + PhysicsDirectSpaceState2D::ShapeResult sr[MAX_INTERSECT_AREAS]; - //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; + int areas = space_state->intersect_point(global_pos, sr, MAX_INTERSECT_AREAS, Set<RID>(), area_mask, false, true); - //screen in global is used for attenuation - Vector2 screen_in_global = to_screen.affine_inverse().xform(screen_size * 0.5); + for (int i = 0; i < areas; i++) { + Area2D *area2d = Object::cast_to<Area2D>(sr[i].collider); + if (!area2d) { + continue; + } - float dist = global_pos.distance_to(screen_in_global); //distance to screen center + if (!area2d->is_overriding_audio_bus()) { + continue; + } - if (dist > max_distance) - continue; //can't hear this sound in this viewport + return area2d->get_audio_bus_name(); + } + return default_bus; +} - float multiplier = Math::pow(1.0f - dist / max_distance, attenuation); - multiplier *= Math::db2linear(volume_db); //also apply player volume! +void AudioStreamPlayer2D::_update_panning() { + if (!active.is_set() || stream.is_null()) { + return; + } - //point in screen is used for panning - Vector2 point_in_screen = to_screen.xform(global_pos); + Ref<World2D> world_2d = get_world_2d(); + ERR_FAIL_COND(world_2d.is_null()); - float pan = CLAMP(point_in_screen.x / screen_size.width, 0.0, 1.0); + Vector2 global_pos = get_global_position(); - float l = 1.0 - pan; - float r = pan; + 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! - 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; - } - } + 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); - output_count = new_output_count; - output_ready = true; + for (Viewport *vp : viewports) { + if (!vp->is_audio_listener_2d()) { + continue; } - - //start playing if requested - if (setplay >= 0.0) { - setseek = setplay; - active = true; - setplay = -1; - //do not update, this makes it easier to animate (will shut off otherwise) - //_change_notify("playing"); //update property in editor + //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; } - //stop playing if no longer active - if (!active) { - set_physics_process_internal(false); - //do not update, this makes it easier to animate (will shut off otherwise) - //_change_notify("playing"); //update property in editor - emit_signal("finished"); + float dist = global_pos.distance_to(listener_in_global); // Distance to listener, or screen if none. + + if (dist > max_distance) { + continue; //can't hear this sound in this viewport } - } -} -void AudioStreamPlayer2D::set_stream(Ref<AudioStream> p_stream) { + float multiplier = Math::pow(1.0f - dist / max_distance, attenuation); + multiplier *= Math::db2linear(volume_db); //also apply player volume! - AudioServer::get_singleton()->lock(); + float pan = CLAMP((relative_to_listener.x + screen_size.x * 0.5) / screen_size.x, 0.0, 1.0); - mix_buffer.resize(AudioServer::get_singleton()->thread_get_mix_buffer_size()); + float l = 1.0 - pan; + float r = pan; - if (stream_playback.is_valid()) { - stream_playback.unref(); - stream.unref(); - active = false; - setseek = -1; + volume_vector.write[0] = AudioFrame(l, r) * multiplier; } - if (p_stream.is_valid()) { - stream = p_stream; - stream_playback = p_stream->instance_playback(); + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_bus_exclusive(playback, _get_actual_bus(), volume_vector); } - AudioServer::get_singleton()->unlock(); - - if (p_stream.is_valid() && stream_playback.is_null()) { - stream.unref(); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_pitch_scale(playback, pitch_scale); } + + last_mix_count = AudioServer::get_singleton()->get_mix_count(); } -Ref<AudioStream> AudioStreamPlayer2D::get_stream() const { +void AudioStreamPlayer2D::set_stream(Ref<AudioStream> p_stream) { + stop(); + stream = p_stream; +} +Ref<AudioStream> AudioStreamPlayer2D::get_stream() const { return stream; } void AudioStreamPlayer2D::set_volume_db(float p_volume) { - volume_db = p_volume; } -float AudioStreamPlayer2D::get_volume_db() const { +float AudioStreamPlayer2D::get_volume_db() const { return volume_db; } 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 { return pitch_scale; } 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()) { - active = true; - setplay = p_from_pos; - output_ready = false; - 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 = p_seconds; + if (is_playing()) { + stop(); + play(p_seconds); } } void AudioStreamPlayer2D::stop() { - - if (stream_playback.is_valid()) { - active = false; - set_physics_process_internal(false); - setplay = -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; // && stream_playback->is_playing(); + 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()) { - 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 { +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) { - autoplay = p_enable; } -bool AudioStreamPlayer2D::is_autoplay_enabled() { +bool AudioStreamPlayer2D::is_autoplay_enabled() { return autoplay; } void AudioStreamPlayer2D::_set_playing(bool p_enable) { - - if (p_enable) + if (p_enable) { play(); - else + } else { stop(); + } } -bool AudioStreamPlayer2D::_is_active() const { - return active; +bool AudioStreamPlayer2D::_is_active() const { + return active.is_set(); } void AudioStreamPlayer2D::_validate_property(PropertyInfo &property) const { - if (property.name == "bus") { - String options; for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) { - if (i > 0) + if (i > 0) { options += ","; + } String name = AudioServer::get_singleton()->get_bus_name(i); options += name; } @@ -419,60 +320,67 @@ void AudioStreamPlayer2D::_validate_property(PropertyInfo &property) const { } void AudioStreamPlayer2D::_bus_layout_changed() { - - _change_notify(); + notify_property_list_changed(); } void AudioStreamPlayer2D::set_max_distance(float p_pixels) { - ERR_FAIL_COND(p_pixels <= 0.0); max_distance = p_pixels; } float AudioStreamPlayer2D::get_max_distance() const { - return max_distance; } void AudioStreamPlayer2D::set_attenuation(float p_curve) { - attenuation = p_curve; } -float AudioStreamPlayer2D::get_attenuation() const { +float AudioStreamPlayer2D::get_attenuation() const { return attenuation; } void AudioStreamPlayer2D::set_area_mask(uint32_t p_mask) { - area_mask = p_mask; } uint32_t AudioStreamPlayer2D::get_area_mask() const { - return area_mask; } 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::_bind_methods() { +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() { ClassDB::bind_method(D_METHOD("set_stream", "stream"), &AudioStreamPlayer2D::set_stream); ClassDB::bind_method(D_METHOD("get_stream"), &AudioStreamPlayer2D::get_stream); @@ -510,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"); @@ -518,8 +429,9 @@ void AudioStreamPlayer2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_EXP_RANGE, "1,4096,1,or_greater"), "set_max_distance", "get_max_distance"); + 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"); @@ -527,22 +439,6 @@ void AudioStreamPlayer2D::_bind_methods() { } AudioStreamPlayer2D::AudioStreamPlayer2D() { - - volume_db = 0; - pitch_scale = 1.0; - autoplay = false; - setseek = -1; - active = false; - output_count = 0; - prev_output_count = 0; - max_distance = 2000; - attenuation = 1; - setplay = -1; - output_ready = false; - area_mask = 1; - stream_paused = false; - stream_paused_fade_in = false; - stream_paused_fade_out = false; AudioServer::get_singleton()->connect("bus_layout_changed", callable_mp(this, &AudioStreamPlayer2D::_bus_layout_changed)); } diff --git a/scene/2d/audio_stream_player_2d.h b/scene/2d/audio_stream_player_2d.h index 86e931b3c6..5360fd4934 100644 --- a/scene/2d/audio_stream_player_2d.h +++ b/scene/2d/audio_stream_player_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -36,7 +36,6 @@ #include "servers/audio_server.h" class AudioStreamPlayer2D : public Node2D { - GDCLASS(AudioStreamPlayer2D, Node2D); private: @@ -47,51 +46,43 @@ private: }; struct Output { - AudioFrame vol; - int bus_index; - Viewport *viewport; //pointer only used for reference to previous mix + int bus_index = 0; + Viewport *viewport = nullptr; //pointer only used for reference to previous mix }; - Output outputs[MAX_OUTPUTS]; - volatile int output_count; - volatile bool 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; - - Ref<AudioStreamPlayback> stream_playback; + Vector<Ref<AudioStreamPlayback>> stream_playbacks; Ref<AudioStream> stream; - Vector<AudioFrame> mix_buffer; - volatile float setseek; - volatile bool active; - volatile float setplay; + SafeFlag active{ false }; + SafeNumeric<float> setplay{ -1.0 }; - float volume_db; - float pitch_scale; - bool autoplay; - bool stream_paused; - bool stream_paused_fade_in; - bool stream_paused_fade_out; - StringName bus; + Vector<AudioFrame> volume_vector; - void _mix_audio(); - static void _mix_audios(void *self) { reinterpret_cast<AudioStreamPlayer2D *>(self)->_mix_audio(); } + uint64_t last_mix_count = -1; + + float volume_db = 0.0; + float pitch_scale = 1.0; + bool autoplay = false; + 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(); - uint32_t area_mask; + static void _listener_changed_cb(void *self) { reinterpret_cast<AudioStreamPlayer2D *>(self)->_update_panning(); } + + uint32_t area_mask = 1; - float max_distance; - float attenuation; + float max_distance = 2000.0; + float attenuation = 1.0; protected: - void _validate_property(PropertyInfo &property) const; + void _validate_property(PropertyInfo &property) const override; void _notification(int p_what); static void _bind_methods(); @@ -129,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/back_buffer_copy.cpp b/scene/2d/back_buffer_copy.cpp index 4c952b7ca6..539a66b881 100644 --- a/scene/2d/back_buffer_copy.cpp +++ b/scene/2d/back_buffer_copy.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,19 +31,14 @@ #include "back_buffer_copy.h" void BackBufferCopy::_update_copy_mode() { - switch (copy_mode) { - case COPY_MODE_DISABLED: { - RS::get_singleton()->canvas_item_set_copy_to_backbuffer(get_canvas_item(), false, Rect2()); } break; case COPY_MODE_RECT: { - RS::get_singleton()->canvas_item_set_copy_to_backbuffer(get_canvas_item(), true, rect); } break; case COPY_MODE_VIEWPORT: { - RS::get_singleton()->canvas_item_set_copy_to_backbuffer(get_canvas_item(), true, Rect2()); } break; @@ -52,7 +47,6 @@ void BackBufferCopy::_update_copy_mode() { #ifdef TOOLS_ENABLED Rect2 BackBufferCopy::_edit_get_rect() const { - return rect; } @@ -62,12 +56,10 @@ bool BackBufferCopy::_edit_use_rect() const { #endif Rect2 BackBufferCopy::get_anchorable_rect() const { - return rect; } void BackBufferCopy::set_rect(const Rect2 &p_rect) { - rect = p_rect; _update_copy_mode(); } @@ -77,17 +69,15 @@ Rect2 BackBufferCopy::get_rect() const { } void BackBufferCopy::set_copy_mode(CopyMode p_mode) { - copy_mode = p_mode; _update_copy_mode(); } -BackBufferCopy::CopyMode BackBufferCopy::get_copy_mode() const { +BackBufferCopy::CopyMode BackBufferCopy::get_copy_mode() const { return copy_mode; } void BackBufferCopy::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_rect", "rect"), &BackBufferCopy::set_rect); ClassDB::bind_method(D_METHOD("get_rect"), &BackBufferCopy::get_rect); @@ -103,10 +93,8 @@ void BackBufferCopy::_bind_methods() { } BackBufferCopy::BackBufferCopy() { - - rect = Rect2(-100, -100, 200, 200); - copy_mode = COPY_MODE_RECT; _update_copy_mode(); } + BackBufferCopy::~BackBufferCopy() { } diff --git a/scene/2d/back_buffer_copy.h b/scene/2d/back_buffer_copy.h index 4b26bf1bdc..6bdb3aaab2 100644 --- a/scene/2d/back_buffer_copy.h +++ b/scene/2d/back_buffer_copy.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -44,8 +44,8 @@ public: }; private: - Rect2 rect; - CopyMode copy_mode; + Rect2 rect = Rect2(-100, -100, 200, 200); + CopyMode copy_mode = COPY_MODE_RECT; void _update_copy_mode(); @@ -54,13 +54,13 @@ protected: public: #ifdef TOOLS_ENABLED - Rect2 _edit_get_rect() const; - virtual bool _edit_use_rect() const; + Rect2 _edit_get_rect() const override; + virtual bool _edit_use_rect() const override; #endif void set_rect(const Rect2 &p_rect); Rect2 get_rect() const; - Rect2 get_anchorable_rect() const; + Rect2 get_anchorable_rect() const override; void set_copy_mode(CopyMode p_mode); CopyMode get_copy_mode() const; diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp index d8af14a3fb..bf91ce8e65 100644 --- a/scene/2d/camera_2d.cpp +++ b/scene/2d/camera_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,45 +30,41 @@ #include "camera_2d.h" -#include "core/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()) + if (!is_inside_tree()) { return; + } if (Engine::get_singleton()->is_editor_hint()) { update(); //will just be drawn return; } - if (!viewport) + if (!viewport) { return; + } if (current) { - ERR_FAIL_COND(custom_viewport && !ObjectDB::get_instance(custom_viewport_id)); Transform2D xform = get_camera_transform(); viewport->set_canvas_transform(xform); - Size2 screen_size = viewport->get_visible_rect().size; + Size2 screen_size = _get_camera_screen_size(); Point2 screen_offset = (anchor_mode == ANCHOR_MODE_DRAG_CENTER ? (screen_size * 0.5) : Point2()); get_tree()->call_group_flags(SceneTree::GROUP_CALL_REALTIME, group_name, "_camera_moved", xform, screen_offset); }; } -void Camera2D::_update_process_mode() { - +void Camera2D::_update_process_callback() { if (Engine::get_singleton()->is_editor_hint()) { set_process_internal(false); set_physics_process_internal(false); - } else if (process_mode == CAMERA2D_PROCESS_IDLE) { + } else if (process_callback == CAMERA2D_PROCESS_IDLE) { set_process_internal(true); set_physics_process_internal(false); } else { @@ -78,6 +74,8 @@ void Camera2D::_update_process_mode() { } void Camera2D::set_zoom(const Vector2 &p_zoom) { + // Setting zoom to zero causes 'affine_invert' issues + ERR_FAIL_COND_MSG(Math::is_zero_approx(p_zoom.x) || Math::is_zero_approx(p_zoom.y), "Zoom level must be different from 0 (can be negative)."); zoom = p_zoom; Point2 old_smoothed_camera_pos = smoothed_camera_pos; @@ -86,58 +84,51 @@ void Camera2D::set_zoom(const Vector2 &p_zoom) { }; Vector2 Camera2D::get_zoom() const { - return zoom; }; Transform2D Camera2D::get_camera_transform() { - - if (!get_tree()) + if (!get_tree()) { return Transform2D(); + } ERR_FAIL_COND_V(custom_viewport && !ObjectDB::get_instance(custom_viewport_id), Transform2D()); - Size2 screen_size = viewport->get_visible_rect().size; + 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) { - if (anchor_mode == ANCHOR_MODE_DRAG_CENTER) { - - if (h_drag_enabled && !Engine::get_singleton()->is_editor_hint() && !h_offset_changed) { - camera_pos.x = MIN(camera_pos.x, (new_camera_pos.x + screen_size.x * 0.5 * zoom.x * drag_margin[MARGIN_LEFT])); - camera_pos.x = MAX(camera_pos.x, (new_camera_pos.x - screen_size.x * 0.5 * zoom.x * drag_margin[MARGIN_RIGHT])); + if (drag_horizontal_enabled && !Engine::get_singleton()->is_editor_hint() && !drag_horizontal_offset_changed) { + camera_pos.x = MIN(camera_pos.x, (new_camera_pos.x + screen_size.x * 0.5 * zoom.x * drag_margin[SIDE_LEFT])); + camera_pos.x = MAX(camera_pos.x, (new_camera_pos.x - screen_size.x * 0.5 * zoom.x * drag_margin[SIDE_RIGHT])); } else { - - if (h_ofs < 0) { - camera_pos.x = new_camera_pos.x + screen_size.x * 0.5 * drag_margin[MARGIN_RIGHT] * h_ofs; + if (drag_horizontal_offset < 0) { + camera_pos.x = new_camera_pos.x + screen_size.x * 0.5 * drag_margin[SIDE_RIGHT] * drag_horizontal_offset; } else { - camera_pos.x = new_camera_pos.x + screen_size.x * 0.5 * drag_margin[MARGIN_LEFT] * h_ofs; + camera_pos.x = new_camera_pos.x + screen_size.x * 0.5 * drag_margin[SIDE_LEFT] * drag_horizontal_offset; } - h_offset_changed = false; + drag_horizontal_offset_changed = false; } - if (v_drag_enabled && !Engine::get_singleton()->is_editor_hint() && !v_offset_changed) { - - camera_pos.y = MIN(camera_pos.y, (new_camera_pos.y + screen_size.y * 0.5 * zoom.y * drag_margin[MARGIN_TOP])); - camera_pos.y = MAX(camera_pos.y, (new_camera_pos.y - screen_size.y * 0.5 * zoom.y * drag_margin[MARGIN_BOTTOM])); + if (drag_vertical_enabled && !Engine::get_singleton()->is_editor_hint() && !drag_vertical_offset_changed) { + camera_pos.y = MIN(camera_pos.y, (new_camera_pos.y + screen_size.y * 0.5 * zoom.y * drag_margin[SIDE_TOP])); + camera_pos.y = MAX(camera_pos.y, (new_camera_pos.y - screen_size.y * 0.5 * zoom.y * drag_margin[SIDE_BOTTOM])); } else { - - if (v_ofs < 0) { - camera_pos.y = new_camera_pos.y + screen_size.y * 0.5 * drag_margin[MARGIN_BOTTOM] * v_ofs; + if (drag_vertical_offset < 0) { + camera_pos.y = new_camera_pos.y + screen_size.y * 0.5 * drag_margin[SIDE_BOTTOM] * drag_vertical_offset; } else { - camera_pos.y = new_camera_pos.y + screen_size.y * 0.5 * drag_margin[MARGIN_TOP] * v_ofs; + camera_pos.y = new_camera_pos.y + screen_size.y * 0.5 * drag_margin[SIDE_TOP] * drag_vertical_offset; } - v_offset_changed = false; + drag_vertical_offset_changed = false; } } else if (anchor_mode == ANCHOR_MODE_FIXED_TOP_LEFT) { - camera_pos = new_camera_pos; } @@ -145,27 +136,29 @@ Transform2D Camera2D::get_camera_transform() { Rect2 screen_rect(-screen_offset + camera_pos, screen_size * zoom); if (limit_smoothing_enabled) { - if (screen_rect.position.x < limit[MARGIN_LEFT]) - camera_pos.x -= screen_rect.position.x - limit[MARGIN_LEFT]; + if (screen_rect.position.x < limit[SIDE_LEFT]) { + camera_pos.x -= screen_rect.position.x - limit[SIDE_LEFT]; + } - if (screen_rect.position.x + screen_rect.size.x > limit[MARGIN_RIGHT]) - camera_pos.x -= screen_rect.position.x + screen_rect.size.x - limit[MARGIN_RIGHT]; + if (screen_rect.position.x + screen_rect.size.x > limit[SIDE_RIGHT]) { + camera_pos.x -= screen_rect.position.x + screen_rect.size.x - limit[SIDE_RIGHT]; + } - if (screen_rect.position.y + screen_rect.size.y > limit[MARGIN_BOTTOM]) - camera_pos.y -= screen_rect.position.y + screen_rect.size.y - limit[MARGIN_BOTTOM]; + if (screen_rect.position.y + screen_rect.size.y > limit[SIDE_BOTTOM]) { + camera_pos.y -= screen_rect.position.y + screen_rect.size.y - limit[SIDE_BOTTOM]; + } - if (screen_rect.position.y < limit[MARGIN_TOP]) - camera_pos.y -= screen_rect.position.y - limit[MARGIN_TOP]; + if (screen_rect.position.y < limit[SIDE_TOP]) { + camera_pos.y -= screen_rect.position.y - limit[SIDE_TOP]; + } } if (smoothing_enabled && !Engine::get_singleton()->is_editor_hint()) { - - float c = smoothing * (process_mode == CAMERA2D_PROCESS_PHYSICS ? get_physics_process_delta_time() : get_process_delta_time()); + real_t c = smoothing * (process_callback == CAMERA2D_PROCESS_PHYSICS ? get_physics_process_delta_time() : get_process_delta_time()); smoothed_camera_pos = ((camera_pos - smoothed_camera_pos) * c) + smoothed_camera_pos; ret_camera_pos = smoothed_camera_pos; //camera_pos=camera_pos*(1.0-smoothing)+new_camera_pos*smoothing; } else { - ret_camera_pos = smoothed_camera_pos = camera_pos; } @@ -176,34 +169,42 @@ Transform2D Camera2D::get_camera_transform() { Point2 screen_offset = (anchor_mode == ANCHOR_MODE_DRAG_CENTER ? (screen_size * 0.5 * zoom) : Point2()); - float angle = get_global_transform().get_rotation(); + real_t angle = get_global_rotation(); if (rotating) { screen_offset = screen_offset.rotated(angle); } Rect2 screen_rect(-screen_offset + ret_camera_pos, screen_size * zoom); - if (screen_rect.position.x < limit[MARGIN_LEFT]) - screen_rect.position.x = limit[MARGIN_LEFT]; - if (screen_rect.position.x + screen_rect.size.x > limit[MARGIN_RIGHT]) - screen_rect.position.x = limit[MARGIN_RIGHT] - screen_rect.size.x; + if (!limit_smoothing_enabled) { + if (screen_rect.position.x < limit[SIDE_LEFT]) { + screen_rect.position.x = limit[SIDE_LEFT]; + } - if (screen_rect.position.y + screen_rect.size.y > limit[MARGIN_BOTTOM]) - screen_rect.position.y = limit[MARGIN_BOTTOM] - screen_rect.size.y; + if (screen_rect.position.x + screen_rect.size.x > limit[SIDE_RIGHT]) { + screen_rect.position.x = limit[SIDE_RIGHT] - screen_rect.size.x; + } - if (screen_rect.position.y < limit[MARGIN_TOP]) - screen_rect.position.y = limit[MARGIN_TOP]; + if (screen_rect.position.y + screen_rect.size.y > limit[SIDE_BOTTOM]) { + screen_rect.position.y = limit[SIDE_BOTTOM] - screen_rect.size.y; + } - if (offset != Vector2()) + if (screen_rect.position.y < limit[SIDE_TOP]) { + screen_rect.position.y = limit[SIDE_TOP]; + } + } + + if (offset != Vector2()) { screen_rect.position += offset; + } camera_screen_center = screen_rect.position + screen_rect.size * 0.5; Transform2D xform; + xform.scale_basis(zoom); if (rotating) { xform.set_rotation(angle); } - xform.scale_basis(zoom); xform.set_origin(screen_rect.position /*.floor()*/); /* @@ -218,29 +219,29 @@ Transform2D Camera2D::get_camera_transform() { } void Camera2D::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_INTERNAL_PROCESS: case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { - _update_scroll(); } break; case NOTIFICATION_TRANSFORM_CHANGED: { - - if (!is_processing_internal() && !is_physics_processing_internal()) + if (!is_processing_internal() && !is_physics_processing_internal()) { _update_scroll(); + } } break; case NOTIFICATION_ENTER_TREE: { - if (custom_viewport && ObjectDB::get_instance(custom_viewport_id)) { viewport = custom_viewport; } else { viewport = get_viewport(); } + if (is_current()) { + viewport->_camera_2d_set(this); + } + canvas = get_canvas(); RID vp = viewport->get_viewport_rid(); @@ -250,16 +251,17 @@ void Camera2D::_notification(int p_what) { add_to_group(group_name); add_to_group(canvas_group_name); - _update_process_mode(); + _update_process_callback(); _update_scroll(); first = true; } break; case NOTIFICATION_EXIT_TREE: { - 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); @@ -267,21 +269,21 @@ void Camera2D::_notification(int p_what) { viewport = nullptr; } break; +#ifdef TOOLS_ENABLED case NOTIFICATION_DRAW: { - - if (!is_inside_tree() || !Engine::get_singleton()->is_editor_hint()) + if (!is_inside_tree() || !Engine::get_singleton()->is_editor_hint()) { break; + } if (screen_drawing_enabled) { - Color area_axis_color(0.5, 0.42, 0.87, 0.63); - float area_axis_width = 1; + Color area_axis_color(1, 0.4, 1, 0.63); + real_t area_axis_width = 1; if (is_current()) { area_axis_width = 3; - area_axis_color.a = 0.83; } Transform2D inv_camera_transform = get_camera_transform().affine_inverse(); - Size2 screen_size = get_viewport_rect().size; + Size2 screen_size = _get_camera_screen_size(); Vector2 screen_endpoints[4] = { inv_camera_transform.xform(Vector2(0, 0)), @@ -298,20 +300,19 @@ void Camera2D::_notification(int p_what) { } if (limit_drawing_enabled) { - Color limit_drawing_color(1, 1, 0, 0.63); - float limit_drawing_width = 1; + Color limit_drawing_color(1, 1, 0.25, 0.63); + real_t limit_drawing_width = 1; if (is_current()) { - limit_drawing_color.a = 0.83; 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[MARGIN_LEFT], limit[MARGIN_TOP]) - camera_origin) / camera_scale, - (Vector2(limit[MARGIN_RIGHT], limit[MARGIN_TOP]) - camera_origin) / camera_scale, - (Vector2(limit[MARGIN_RIGHT], limit[MARGIN_BOTTOM]) - camera_origin) / camera_scale, - (Vector2(limit[MARGIN_LEFT], limit[MARGIN_BOTTOM]) - camera_origin) / camera_scale + (Vector2(limit[SIDE_LEFT], limit[SIDE_TOP]) - camera_origin) / camera_scale, + (Vector2(limit[SIDE_RIGHT], limit[SIDE_TOP]) - camera_origin) / camera_scale, + (Vector2(limit[SIDE_RIGHT], limit[SIDE_BOTTOM]) - camera_origin) / camera_scale, + (Vector2(limit[SIDE_LEFT], limit[SIDE_BOTTOM]) - camera_origin) / camera_scale }; for (int i = 0; i < 4; i++) { @@ -320,21 +321,20 @@ void Camera2D::_notification(int p_what) { } if (margin_drawing_enabled) { - Color margin_drawing_color(0, 1, 1, 0.63); - float margin_drawing_width = 1; + Color margin_drawing_color(0.25, 1, 1, 0.63); + real_t margin_drawing_width = 1; if (is_current()) { margin_drawing_width = 3; - margin_drawing_color.a = 0.83; } Transform2D inv_camera_transform = get_camera_transform().affine_inverse(); - Size2 screen_size = get_viewport_rect().size; + Size2 screen_size = _get_camera_screen_size(); Vector2 margin_endpoints[4] = { - inv_camera_transform.xform(Vector2((screen_size.width / 2) - ((screen_size.width / 2) * drag_margin[MARGIN_LEFT]), (screen_size.height / 2) - ((screen_size.height / 2) * drag_margin[MARGIN_TOP]))), - inv_camera_transform.xform(Vector2((screen_size.width / 2) + ((screen_size.width / 2) * drag_margin[MARGIN_RIGHT]), (screen_size.height / 2) - ((screen_size.height / 2) * drag_margin[MARGIN_TOP]))), - inv_camera_transform.xform(Vector2((screen_size.width / 2) + ((screen_size.width / 2) * drag_margin[MARGIN_RIGHT]), (screen_size.height / 2) + ((screen_size.height / 2) * drag_margin[MARGIN_BOTTOM]))), - inv_camera_transform.xform(Vector2((screen_size.width / 2) - ((screen_size.width / 2) * drag_margin[MARGIN_LEFT]), (screen_size.height / 2) + ((screen_size.height / 2) * drag_margin[MARGIN_BOTTOM]))) + inv_camera_transform.xform(Vector2((screen_size.width / 2) - ((screen_size.width / 2) * drag_margin[SIDE_LEFT]), (screen_size.height / 2) - ((screen_size.height / 2) * drag_margin[SIDE_TOP]))), + inv_camera_transform.xform(Vector2((screen_size.width / 2) + ((screen_size.width / 2) * drag_margin[SIDE_RIGHT]), (screen_size.height / 2) - ((screen_size.height / 2) * drag_margin[SIDE_TOP]))), + inv_camera_transform.xform(Vector2((screen_size.width / 2) + ((screen_size.width / 2) * drag_margin[SIDE_RIGHT]), (screen_size.height / 2) + ((screen_size.height / 2) * drag_margin[SIDE_BOTTOM]))), + inv_camera_transform.xform(Vector2((screen_size.width / 2) - ((screen_size.width / 2) * drag_margin[SIDE_LEFT]), (screen_size.height / 2) + ((screen_size.height / 2) * drag_margin[SIDE_BOTTOM]))) }; Transform2D inv_transform = get_global_transform().affine_inverse(); // undo global space @@ -343,242 +343,238 @@ void Camera2D::_notification(int p_what) { draw_line(inv_transform.xform(margin_endpoints[i]), inv_transform.xform(margin_endpoints[(i + 1) % 4]), margin_drawing_color, margin_drawing_width); } } - } break; +#endif } } void Camera2D::set_offset(const Vector2 &p_offset) { - offset = p_offset; + Point2 old_smoothed_camera_pos = smoothed_camera_pos; _update_scroll(); + smoothed_camera_pos = old_smoothed_camera_pos; } Vector2 Camera2D::get_offset() const { - return offset; } void Camera2D::set_anchor_mode(AnchorMode p_anchor_mode) { - anchor_mode = p_anchor_mode; _update_scroll(); } Camera2D::AnchorMode Camera2D::get_anchor_mode() const { - return anchor_mode; } void Camera2D::set_rotating(bool p_rotating) { - rotating = p_rotating; + Point2 old_smoothed_camera_pos = smoothed_camera_pos; _update_scroll(); + smoothed_camera_pos = old_smoothed_camera_pos; } bool Camera2D::is_rotating() const { - return rotating; } -void Camera2D::set_process_mode(Camera2DProcessMode p_mode) { - - if (process_mode == p_mode) +void Camera2D::set_process_callback(Camera2DProcessCallback p_mode) { + if (process_callback == p_mode) { return; + } - process_mode = p_mode; - _update_process_mode(); + process_callback = p_mode; + _update_process_callback(); } -Camera2D::Camera2DProcessMode Camera2D::get_process_mode() const { - - return process_mode; +Camera2D::Camera2DProcessCallback Camera2D::get_process_callback() const { + return process_callback; } 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) { - - if (p_current) +void Camera2D::set_current(bool p_current) { + if (p_current) { make_current(); - - current = p_current; - update(); + } else { + if (current) { + clear_current(); + } + } } bool Camera2D::is_current() const { - return current; } 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; } } -void Camera2D::set_limit(Margin p_margin, int p_limit) { - - ERR_FAIL_INDEX((int)p_margin, 4); - limit[p_margin] = p_limit; +void Camera2D::set_limit(Side p_side, int p_limit) { + ERR_FAIL_INDEX((int)p_side, 4); + limit[p_side] = p_limit; update(); } -int Camera2D::get_limit(Margin p_margin) const { - - ERR_FAIL_INDEX_V((int)p_margin, 4, 0); - return limit[p_margin]; +int Camera2D::get_limit(Side p_side) const { + ERR_FAIL_INDEX_V((int)p_side, 4, 0); + return limit[p_side]; } void Camera2D::set_limit_smoothing_enabled(bool enable) { - limit_smoothing_enabled = enable; _update_scroll(); } bool Camera2D::is_limit_smoothing_enabled() const { - return limit_smoothing_enabled; } -void Camera2D::set_drag_margin(Margin p_margin, float p_drag_margin) { - - ERR_FAIL_INDEX((int)p_margin, 4); - drag_margin[p_margin] = p_drag_margin; +void Camera2D::set_drag_margin(Side p_side, real_t p_drag_margin) { + ERR_FAIL_INDEX((int)p_side, 4); + drag_margin[p_side] = p_drag_margin; update(); } -float Camera2D::get_drag_margin(Margin p_margin) const { - - ERR_FAIL_INDEX_V((int)p_margin, 4, 0); - return drag_margin[p_margin]; +real_t Camera2D::get_drag_margin(Side p_side) const { + ERR_FAIL_INDEX_V((int)p_side, 4, 0); + return drag_margin[p_side]; } Vector2 Camera2D::get_camera_position() const { - return camera_pos; } void Camera2D::force_update_scroll() { - _update_scroll(); } void Camera2D::reset_smoothing() { - - smoothed_camera_pos = camera_pos; _update_scroll(); + smoothed_camera_pos = camera_pos; } void Camera2D::align() { - ERR_FAIL_COND(custom_viewport && !ObjectDB::get_instance(custom_viewport_id)); - Size2 screen_size = viewport->get_visible_rect().size; + 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 (h_ofs < 0) { - camera_pos.x = current_camera_pos.x + screen_size.x * 0.5 * drag_margin[MARGIN_RIGHT] * h_ofs; + if (drag_horizontal_offset < 0) { + camera_pos.x = current_camera_pos.x + screen_size.x * 0.5 * drag_margin[SIDE_RIGHT] * drag_horizontal_offset; } else { - camera_pos.x = current_camera_pos.x + screen_size.x * 0.5 * drag_margin[MARGIN_LEFT] * h_ofs; + camera_pos.x = current_camera_pos.x + screen_size.x * 0.5 * drag_margin[SIDE_LEFT] * drag_horizontal_offset; } - if (v_ofs < 0) { - camera_pos.y = current_camera_pos.y + screen_size.y * 0.5 * drag_margin[MARGIN_TOP] * v_ofs; + if (drag_vertical_offset < 0) { + camera_pos.y = current_camera_pos.y + screen_size.y * 0.5 * drag_margin[SIDE_TOP] * drag_vertical_offset; } else { - camera_pos.y = current_camera_pos.y + screen_size.y * 0.5 * drag_margin[MARGIN_BOTTOM] * v_ofs; + camera_pos.y = current_camera_pos.y + screen_size.y * 0.5 * drag_margin[SIDE_BOTTOM] * drag_vertical_offset; } } else if (anchor_mode == ANCHOR_MODE_FIXED_TOP_LEFT) { - camera_pos = current_camera_pos; } _update_scroll(); } -void Camera2D::set_follow_smoothing(float p_speed) { - +void Camera2D::set_follow_smoothing(real_t p_speed) { smoothing = p_speed; - if (smoothing > 0 && !(is_inside_tree() && Engine::get_singleton()->is_editor_hint())) + if (smoothing > 0 && !(is_inside_tree() && Engine::get_singleton()->is_editor_hint())) { set_process_internal(true); - else + } else { set_process_internal(false); + } } -float Camera2D::get_follow_smoothing() const { - +real_t Camera2D::get_follow_smoothing() const { return smoothing; } Point2 Camera2D::get_camera_screen_center() const { - return camera_screen_center; } -void Camera2D::set_h_drag_enabled(bool p_enabled) { - - h_drag_enabled = p_enabled; +Size2 Camera2D::_get_camera_screen_size() const { + // special case if the camera2D is in the root viewport + if (Engine::get_singleton()->is_editor_hint() && get_viewport()->get_parent_viewport() == get_tree()->get_root()) { + return Size2(ProjectSettings::get_singleton()->get("display/window/size/width"), ProjectSettings::get_singleton()->get("display/window/size/height")); + } + return get_viewport_rect().size; } -bool Camera2D::is_h_drag_enabled() const { - - return h_drag_enabled; +void Camera2D::set_drag_horizontal_enabled(bool p_enabled) { + drag_horizontal_enabled = p_enabled; } -void Camera2D::set_v_drag_enabled(bool p_enabled) { - - v_drag_enabled = p_enabled; +bool Camera2D::is_drag_horizontal_enabled() const { + return drag_horizontal_enabled; } -bool Camera2D::is_v_drag_enabled() const { - - return v_drag_enabled; +void Camera2D::set_drag_vertical_enabled(bool p_enabled) { + drag_vertical_enabled = p_enabled; } -void Camera2D::set_v_offset(float p_offset) { +bool Camera2D::is_drag_vertical_enabled() const { + return drag_vertical_enabled; +} - v_ofs = p_offset; - v_offset_changed = true; +void Camera2D::set_drag_vertical_offset(real_t p_offset) { + drag_vertical_offset = p_offset; + drag_vertical_offset_changed = true; + Point2 old_smoothed_camera_pos = smoothed_camera_pos; _update_scroll(); + smoothed_camera_pos = old_smoothed_camera_pos; } -float Camera2D::get_v_offset() const { - - return v_ofs; +real_t Camera2D::get_drag_vertical_offset() const { + return drag_vertical_offset; } -void Camera2D::set_h_offset(float p_offset) { - - h_ofs = p_offset; - h_offset_changed = true; +void Camera2D::set_drag_horizontal_offset(real_t p_offset) { + drag_horizontal_offset = p_offset; + drag_horizontal_offset_changed = true; + Point2 old_smoothed_camera_pos = smoothed_camera_pos; _update_scroll(); + smoothed_camera_pos = old_smoothed_camera_pos; } -float Camera2D::get_h_offset() const { - return h_ofs; +real_t Camera2D::get_drag_horizontal_offset() const { + return drag_horizontal_offset; } -void Camera2D::_set_old_smoothing(float p_enable) { +void Camera2D::_set_old_smoothing(real_t p_enable) { //compatibility if (p_enable > 0) { smoothing_enabled = true; @@ -587,12 +583,11 @@ void Camera2D::_set_old_smoothing(float p_enable) { } void Camera2D::set_enable_follow_smoothing(bool p_enabled) { - smoothing_enabled = p_enabled; + notify_property_list_changed(); } bool Camera2D::is_follow_smoothing_enabled() const { - return smoothing_enabled; } @@ -612,11 +607,11 @@ void Camera2D::set_custom_viewport(Node *p_viewport) { } if (is_inside_tree()) { - - if (custom_viewport) + if (custom_viewport) { viewport = custom_viewport; - else + } else { viewport = get_viewport(); + } RID vp = viewport->get_viewport_rid(); group_name = "__cameras_" + itos(vp.get_id()); @@ -627,13 +622,14 @@ void Camera2D::set_custom_viewport(Node *p_viewport) { } Node *Camera2D::get_custom_viewport() const { - return custom_viewport; } void Camera2D::set_screen_drawing_enabled(bool enable) { screen_drawing_enabled = enable; +#ifdef TOOLS_ENABLED update(); +#endif } bool Camera2D::is_screen_drawing_enabled() const { @@ -642,7 +638,9 @@ bool Camera2D::is_screen_drawing_enabled() const { void Camera2D::set_limit_drawing_enabled(bool enable) { limit_drawing_enabled = enable; +#ifdef TOOLS_ENABLED update(); +#endif } bool Camera2D::is_limit_drawing_enabled() const { @@ -651,15 +649,22 @@ bool Camera2D::is_limit_drawing_enabled() const { void Camera2D::set_margin_drawing_enabled(bool enable) { margin_drawing_enabled = enable; +#ifdef TOOLS_ENABLED update(); +#endif } bool Camera2D::is_margin_drawing_enabled() const { return margin_drawing_enabled; } -void Camera2D::_bind_methods() { +void Camera2D::_validate_property(PropertyInfo &property) const { + if (!smoothing_enabled && property.name == "smoothing_speed") { + property.usage = PROPERTY_USAGE_NOEDITOR; + } +} +void Camera2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_offset", "offset"), &Camera2D::set_offset); ClassDB::bind_method(D_METHOD("get_offset"), &Camera2D::get_offset); @@ -669,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_mode", "mode"), &Camera2D::set_process_mode); - ClassDB::bind_method(D_METHOD("get_process_mode"), &Camera2D::get_process_mode); + 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); @@ -687,17 +689,17 @@ void Camera2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_limit_smoothing_enabled", "limit_smoothing_enabled"), &Camera2D::set_limit_smoothing_enabled); ClassDB::bind_method(D_METHOD("is_limit_smoothing_enabled"), &Camera2D::is_limit_smoothing_enabled); - ClassDB::bind_method(D_METHOD("set_v_drag_enabled", "enabled"), &Camera2D::set_v_drag_enabled); - ClassDB::bind_method(D_METHOD("is_v_drag_enabled"), &Camera2D::is_v_drag_enabled); + ClassDB::bind_method(D_METHOD("set_drag_vertical_enabled", "enabled"), &Camera2D::set_drag_vertical_enabled); + ClassDB::bind_method(D_METHOD("is_drag_vertical_enabled"), &Camera2D::is_drag_vertical_enabled); - ClassDB::bind_method(D_METHOD("set_h_drag_enabled", "enabled"), &Camera2D::set_h_drag_enabled); - ClassDB::bind_method(D_METHOD("is_h_drag_enabled"), &Camera2D::is_h_drag_enabled); + ClassDB::bind_method(D_METHOD("set_drag_horizontal_enabled", "enabled"), &Camera2D::set_drag_horizontal_enabled); + ClassDB::bind_method(D_METHOD("is_drag_horizontal_enabled"), &Camera2D::is_drag_horizontal_enabled); - ClassDB::bind_method(D_METHOD("set_v_offset", "ofs"), &Camera2D::set_v_offset); - ClassDB::bind_method(D_METHOD("get_v_offset"), &Camera2D::get_v_offset); + ClassDB::bind_method(D_METHOD("set_drag_vertical_offset", "offset"), &Camera2D::set_drag_vertical_offset); + ClassDB::bind_method(D_METHOD("get_drag_vertical_offset"), &Camera2D::get_drag_vertical_offset); - ClassDB::bind_method(D_METHOD("set_h_offset", "ofs"), &Camera2D::set_h_offset); - ClassDB::bind_method(D_METHOD("get_h_offset"), &Camera2D::get_h_offset); + ClassDB::bind_method(D_METHOD("set_drag_horizontal_offset", "offset"), &Camera2D::set_drag_horizontal_offset); + ClassDB::bind_method(D_METHOD("get_drag_horizontal_offset"), &Camera2D::get_drag_horizontal_offset); ClassDB::bind_method(D_METHOD("set_drag_margin", "margin", "drag_margin"), &Camera2D::set_drag_margin); ClassDB::bind_method(D_METHOD("get_drag_margin", "margin"), &Camera2D::get_drag_margin); @@ -735,35 +737,31 @@ 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", 0), "set_custom_viewport", "get_custom_viewport"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "process_mode", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_process_mode", "get_process_mode"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "custom_viewport", PROPERTY_HINT_RESOURCE_TYPE, "Viewport", PROPERTY_USAGE_NONE), "set_custom_viewport", "get_custom_viewport"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "process_callback", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_process_callback", "get_process_callback"); ADD_GROUP("Limit", "limit_"); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "limit_left"), "set_limit", "get_limit", MARGIN_LEFT); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "limit_top"), "set_limit", "get_limit", MARGIN_TOP); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "limit_right"), "set_limit", "get_limit", MARGIN_RIGHT); - ADD_PROPERTYI(PropertyInfo(Variant::INT, "limit_bottom"), "set_limit", "get_limit", MARGIN_BOTTOM); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "limit_left"), "set_limit", "get_limit", SIDE_LEFT); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "limit_top"), "set_limit", "get_limit", SIDE_TOP); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "limit_right"), "set_limit", "get_limit", SIDE_RIGHT); + ADD_PROPERTYI(PropertyInfo(Variant::INT, "limit_bottom"), "set_limit", "get_limit", SIDE_BOTTOM); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "limit_smoothed"), "set_limit_smoothing_enabled", "is_limit_smoothing_enabled"); - ADD_GROUP("Draw Margin", "draw_margin_"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_margin_h_enabled"), "set_h_drag_enabled", "is_h_drag_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_margin_v_enabled"), "set_v_drag_enabled", "is_v_drag_enabled"); - ADD_GROUP("Smoothing", "smoothing_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smoothing_enabled"), "set_enable_follow_smoothing", "is_follow_smoothing_enabled"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "smoothing_speed"), "set_follow_smoothing", "get_follow_smoothing"); - ADD_GROUP("Offset", "offset_"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "offset_h", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_h_offset", "get_h_offset"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "offset_v", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_v_offset", "get_v_offset"); - - ADD_GROUP("Drag Margin", "drag_margin_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "drag_margin_left", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_drag_margin", "get_drag_margin", MARGIN_LEFT); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "drag_margin_top", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_drag_margin", "get_drag_margin", MARGIN_TOP); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "drag_margin_right", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_drag_margin", "get_drag_margin", MARGIN_RIGHT); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "drag_margin_bottom", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_drag_margin", "get_drag_margin", MARGIN_BOTTOM); + ADD_GROUP("Drag", "drag_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_horizontal_enabled"), "set_drag_horizontal_enabled", "is_drag_horizontal_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_vertical_enabled"), "set_drag_vertical_enabled", "is_drag_vertical_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "drag_horizontal_offset", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_drag_horizontal_offset", "get_drag_horizontal_offset"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "drag_vertical_offset", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_drag_vertical_offset", "get_drag_vertical_offset"); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "drag_left_margin", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_drag_margin", "get_drag_margin", SIDE_LEFT); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "drag_top_margin", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_drag_margin", "get_drag_margin", SIDE_TOP); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "drag_right_margin", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_drag_margin", "get_drag_margin", SIDE_RIGHT); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "drag_bottom_margin", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_drag_margin", "get_drag_margin", SIDE_BOTTOM); ADD_GROUP("Editor", "editor_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editor_draw_screen"), "set_screen_drawing_enabled", "is_screen_drawing_enabled"); @@ -777,40 +775,15 @@ void Camera2D::_bind_methods() { } Camera2D::Camera2D() { - - anchor_mode = ANCHOR_MODE_DRAG_CENTER; - rotating = false; - current = false; - limit[MARGIN_LEFT] = -10000000; - limit[MARGIN_TOP] = -10000000; - limit[MARGIN_RIGHT] = 10000000; - limit[MARGIN_BOTTOM] = 10000000; - - drag_margin[MARGIN_LEFT] = 0.2; - drag_margin[MARGIN_TOP] = 0.2; - drag_margin[MARGIN_RIGHT] = 0.2; - drag_margin[MARGIN_BOTTOM] = 0.2; - camera_pos = Vector2(); - first = true; - smoothing_enabled = false; - limit_smoothing_enabled = false; - custom_viewport = nullptr; - - process_mode = CAMERA2D_PROCESS_IDLE; - - smoothing = 5.0; - zoom = Vector2(1, 1); - - screen_drawing_enabled = true; - limit_drawing_enabled = false; - margin_drawing_enabled = false; - - h_drag_enabled = false; - v_drag_enabled = false; - h_ofs = 0; - v_ofs = 0; - h_offset_changed = false; - v_offset_changed = false; + limit[SIDE_LEFT] = -10000000; + limit[SIDE_TOP] = -10000000; + limit[SIDE_RIGHT] = 10000000; + limit[SIDE_BOTTOM] = 10000000; + + drag_margin[SIDE_LEFT] = 0.2; + drag_margin[SIDE_TOP] = 0.2; + drag_margin[SIDE_RIGHT] = 0.2; + drag_margin[SIDE_BOTTOM] = 0.2; set_notify_transform(true); } diff --git a/scene/2d/camera_2d.h b/scene/2d/camera_2d.h index 7a106ef79a..d697515547 100644 --- a/scene/2d/camera_2d.h +++ b/scene/2d/camera_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,10 +32,8 @@ #define CAMERA_2D_H #include "scene/2d/node_2d.h" -#include "scene/main/window.h" class Camera2D : public Node2D { - GDCLASS(Camera2D, Node2D); public: @@ -44,7 +42,7 @@ public: ANCHOR_MODE_DRAG_CENTER }; - enum Camera2DProcessMode { + enum Camera2DProcessCallback { CAMERA2D_PROCESS_PHYSICS, CAMERA2D_PROCESS_IDLE }; @@ -52,53 +50,56 @@ public: protected: Point2 camera_pos; Point2 smoothed_camera_pos; - bool first; + bool first = true; ObjectID custom_viewport_id; // to check validity - Viewport *custom_viewport; - Viewport *viewport; + Viewport *custom_viewport = nullptr; + Viewport *viewport = nullptr; StringName group_name; StringName canvas_group_name; RID canvas; Vector2 offset; - Vector2 zoom; - AnchorMode anchor_mode; - bool rotating; - bool current; - float smoothing; - bool smoothing_enabled; + Vector2 zoom = Vector2(1, 1); + AnchorMode anchor_mode = ANCHOR_MODE_DRAG_CENTER; + bool rotating = false; + bool current = false; + real_t smoothing = 5.0; + bool smoothing_enabled = false; int limit[4]; - bool limit_smoothing_enabled; - float drag_margin[4]; - - bool h_drag_enabled; - bool v_drag_enabled; - float h_ofs; - float v_ofs; + bool limit_smoothing_enabled = false; - bool h_offset_changed; - bool v_offset_changed; + real_t drag_margin[4]; + bool drag_horizontal_enabled = false; + bool drag_vertical_enabled = false; + real_t drag_horizontal_offset = 0.0; + real_t drag_vertical_offset = 0.0; + bool drag_horizontal_offset_changed = false; + bool drag_vertical_offset_changed = false; Point2 camera_screen_center; - void _update_process_mode(); + void _update_process_callback(); 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(float p_enable); + void _set_old_smoothing(real_t p_enable); - bool screen_drawing_enabled; - bool limit_drawing_enabled; - bool margin_drawing_enabled; + bool screen_drawing_enabled = true; + bool limit_drawing_enabled = false; + bool margin_drawing_enabled = false; - Camera2DProcessMode process_mode; + Camera2DProcessCallback process_callback = CAMERA2D_PROCESS_IDLE; + + Size2 _get_camera_screen_size() const; protected: virtual Transform2D get_camera_transform(); + void _notification(int p_what); static void _bind_methods(); + void _validate_property(PropertyInfo &property) const override; public: void set_offset(const Vector2 &p_offset); @@ -110,35 +111,35 @@ public: void set_rotating(bool p_rotating); bool is_rotating() const; - void set_limit(Margin p_margin, int p_limit); - int get_limit(Margin p_margin) const; + void set_limit(Side p_side, int p_limit); + int get_limit(Side p_side) const; void set_limit_smoothing_enabled(bool enable); bool is_limit_smoothing_enabled() const; - void set_h_drag_enabled(bool p_enabled); - bool is_h_drag_enabled() const; + void set_drag_horizontal_enabled(bool p_enabled); + bool is_drag_horizontal_enabled() const; - void set_v_drag_enabled(bool p_enabled); - bool is_v_drag_enabled() const; + void set_drag_vertical_enabled(bool p_enabled); + bool is_drag_vertical_enabled() const; - void set_drag_margin(Margin p_margin, float p_drag_margin); - float get_drag_margin(Margin p_margin) const; + void set_drag_margin(Side p_side, real_t p_drag_margin); + real_t get_drag_margin(Side p_side) const; - void set_v_offset(float p_offset); - float get_v_offset() const; + void set_drag_horizontal_offset(real_t p_offset); + real_t get_drag_horizontal_offset() const; - void set_h_offset(float p_offset); - float get_h_offset() const; + void set_drag_vertical_offset(real_t p_offset); + real_t get_drag_vertical_offset() const; void set_enable_follow_smoothing(bool p_enabled); bool is_follow_smoothing_enabled() const; - void set_follow_smoothing(float p_speed); - float get_follow_smoothing() const; + void set_follow_smoothing(real_t p_speed); + real_t get_follow_smoothing() const; - void set_process_mode(Camera2DProcessMode p_mode); - Camera2DProcessMode get_process_mode() const; + void set_process_callback(Camera2DProcessCallback p_mode); + Camera2DProcessCallback get_process_callback() const; void make_current(); void clear_current(); @@ -170,6 +171,6 @@ public: }; VARIANT_ENUM_CAST(Camera2D::AnchorMode); -VARIANT_ENUM_CAST(Camera2D::Camera2DProcessMode); +VARIANT_ENUM_CAST(Camera2D::Camera2DProcessCallback); #endif // CAMERA_2D_H diff --git a/scene/2d/canvas_group.cpp b/scene/2d/canvas_group.cpp new file mode 100644 index 0000000000..ee025b6dfc --- /dev/null +++ b/scene/2d/canvas_group.cpp @@ -0,0 +1,88 @@ +/*************************************************************************/ +/* canvas_group.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "canvas_group.h" + +void CanvasGroup::set_fit_margin(real_t p_fit_margin) { + ERR_FAIL_COND(p_fit_margin < 0.0); + + fit_margin = p_fit_margin; + RS::get_singleton()->canvas_item_set_canvas_group_mode(get_canvas_item(), RS::CANVAS_GROUP_MODE_TRANSPARENT, clear_margin, true, fit_margin, use_mipmaps); + + update(); +} + +real_t CanvasGroup::get_fit_margin() const { + return fit_margin; +} + +void CanvasGroup::set_clear_margin(real_t p_clear_margin) { + ERR_FAIL_COND(p_clear_margin < 0.0); + + clear_margin = p_clear_margin; + RS::get_singleton()->canvas_item_set_canvas_group_mode(get_canvas_item(), RS::CANVAS_GROUP_MODE_TRANSPARENT, clear_margin, true, clear_margin, use_mipmaps); + + update(); +} + +real_t CanvasGroup::get_clear_margin() const { + return clear_margin; +} + +void CanvasGroup::set_use_mipmaps(bool p_use_mipmaps) { + use_mipmaps = p_use_mipmaps; + RS::get_singleton()->canvas_item_set_canvas_group_mode(get_canvas_item(), RS::CANVAS_GROUP_MODE_TRANSPARENT, clear_margin, true, fit_margin, use_mipmaps); +} +bool CanvasGroup::is_using_mipmaps() const { + return use_mipmaps; +} + +void CanvasGroup::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_fit_margin", "fit_margin"), &CanvasGroup::set_fit_margin); + ClassDB::bind_method(D_METHOD("get_fit_margin"), &CanvasGroup::get_fit_margin); + + ClassDB::bind_method(D_METHOD("set_clear_margin", "clear_margin"), &CanvasGroup::set_clear_margin); + ClassDB::bind_method(D_METHOD("get_clear_margin"), &CanvasGroup::get_clear_margin); + + ClassDB::bind_method(D_METHOD("set_use_mipmaps", "use_mipmaps"), &CanvasGroup::set_use_mipmaps); + ClassDB::bind_method(D_METHOD("is_using_mipmaps"), &CanvasGroup::is_using_mipmaps); + + ADD_GROUP("Tweaks", ""); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fit_margin", PROPERTY_HINT_RANGE, "0,1024,1.0,or_greater"), "set_fit_margin", "get_fit_margin"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "clear_margin", PROPERTY_HINT_RANGE, "0,1024,1.0,or_greater"), "set_clear_margin", "get_clear_margin"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_mipmaps"), "set_use_mipmaps", "is_using_mipmaps"); +} + +CanvasGroup::CanvasGroup() { + set_fit_margin(10.0); //sets things +} +CanvasGroup::~CanvasGroup() { + RS::get_singleton()->canvas_item_set_canvas_group_mode(get_canvas_item(), RS::CANVAS_GROUP_MODE_DISABLED); +} diff --git a/scene/2d/y_sort.h b/scene/2d/canvas_group.h index 62787d6744..b487d7a098 100644 --- a/scene/2d/y_sort.h +++ b/scene/2d/canvas_group.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* y_sort.h */ +/* canvas_group.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,20 +28,32 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef Y_SORT_H -#define Y_SORT_H +#ifndef CANVASGROUP_H +#define CANVASGROUP_H #include "scene/2d/node_2d.h" -class YSort : public Node2D { - GDCLASS(YSort, Node2D); - bool sort_enabled; +class CanvasGroup : public Node2D { + GDCLASS(CanvasGroup, Node2D) + real_t fit_margin = 10.0; + real_t clear_margin = 10.0; + bool use_mipmaps = false; + +protected: static void _bind_methods(); public: - void set_sort_enabled(bool p_enabled); - bool is_sort_enabled() const; - YSort(); + void set_fit_margin(real_t p_fit_margin); + real_t get_fit_margin() const; + + void set_clear_margin(real_t p_clear_margin); + real_t get_clear_margin() const; + + void set_use_mipmaps(bool p_use_mipmaps); + bool is_using_mipmaps() const; + + CanvasGroup(); + ~CanvasGroup(); }; -#endif // Y_SORT_H +#endif // CANVASGROUP_H diff --git a/scene/2d/canvas_modulate.cpp b/scene/2d/canvas_modulate.cpp index 05f8804e2a..4de99959a3 100644 --- a/scene/2d/canvas_modulate.cpp +++ b/scene/2d/canvas_modulate.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,22 +31,18 @@ #include "canvas_modulate.h" void CanvasModulate::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_CANVAS) { - if (is_visible_in_tree()) { RS::get_singleton()->canvas_set_modulate(get_canvas(), color); add_to_group("_canvas_modulate_" + itos(get_canvas().get_id())); } } else if (p_what == NOTIFICATION_EXIT_CANVAS) { - if (is_visible_in_tree()) { RS::get_singleton()->canvas_set_modulate(get_canvas(), Color(1, 1, 1, 1)); remove_from_group("_canvas_modulate_" + itos(get_canvas().get_id())); } } else if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - if (is_visible_in_tree()) { RS::get_singleton()->canvas_set_modulate(get_canvas(), color); add_to_group("_canvas_modulate_" + itos(get_canvas().get_id())); @@ -55,12 +51,11 @@ void CanvasModulate::_notification(int p_what) { remove_from_group("_canvas_modulate_" + itos(get_canvas().get_id())); } - update_configuration_warning(); + update_configuration_warnings(); } } void CanvasModulate::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_color", "color"), &CanvasModulate::set_color); ClassDB::bind_method(D_METHOD("get_color"), &CanvasModulate::get_color); @@ -68,34 +63,32 @@ void CanvasModulate::_bind_methods() { } void CanvasModulate::set_color(const Color &p_color) { - color = p_color; if (is_visible_in_tree()) { RS::get_singleton()->canvas_set_modulate(get_canvas(), color); } } -Color CanvasModulate::get_color() const { +Color CanvasModulate::get_color() const { return color; } -String CanvasModulate::get_configuration_warning() const { +TypedArray<String> CanvasModulate::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); - if (!is_visible_in_tree() || !is_inside_tree()) - return String(); + if (is_visible_in_tree() && is_inside_tree()) { + List<Node *> nodes; + get_tree()->get_nodes_in_group("_canvas_modulate_" + itos(get_canvas().get_id()), &nodes); - List<Node *> nodes; - get_tree()->get_nodes_in_group("_canvas_modulate_" + itos(get_canvas().get_id()), &nodes); - - if (nodes.size() > 1) { - return TTR("Only one visible CanvasModulate is allowed per scene (or set of instanced scenes). The first created one will work, while the rest will be ignored."); + if (nodes.size() > 1) { + warnings.push_back(TTR("Only one visible CanvasModulate is allowed per scene (or set of instantiated scenes). The first created one will work, while the rest will be ignored.")); + } } - return String(); + return warnings; } CanvasModulate::CanvasModulate() { - color = Color(1, 1, 1, 1); } CanvasModulate::~CanvasModulate() { diff --git a/scene/2d/canvas_modulate.h b/scene/2d/canvas_modulate.h index ada6e27760..3d85a92a11 100644 --- a/scene/2d/canvas_modulate.h +++ b/scene/2d/canvas_modulate.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -34,10 +34,9 @@ #include "scene/2d/node_2d.h" class CanvasModulate : public Node2D { - GDCLASS(CanvasModulate, Node2D); - Color color; + Color color = Color(1, 1, 1, 1); protected: void _notification(int p_what); @@ -47,7 +46,7 @@ public: void set_color(const Color &p_color); Color get_color() const; - String get_configuration_warning() const; + TypedArray<String> get_configuration_warnings() const override; CanvasModulate(); ~CanvasModulate(); diff --git a/scene/2d/collision_object_2d.cpp b/scene/2d/collision_object_2d.cpp index d82f4a2f2b..5d3a538f60 100644 --- a/scene/2d/collision_object_2d.cpp +++ b/scene/2d/collision_object_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,79 +31,232 @@ #include "collision_object_2d.h" #include "scene/scene_string_names.h" -#include "servers/physics_server_2d.h" void CollisionObject2D::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - Transform2D global_transform = get_global_transform(); - if (area) + if (area) { PhysicsServer2D::get_singleton()->area_set_transform(rid, global_transform); - else + } else { PhysicsServer2D::get_singleton()->body_set_state(rid, PhysicsServer2D::BODY_STATE_TRANSFORM, global_transform); + } - RID space = get_world_2d()->get_space(); - if (area) { - PhysicsServer2D::get_singleton()->area_set_space(rid, space); - } else - PhysicsServer2D::get_singleton()->body_set_space(rid, space); + bool disabled = !is_enabled(); - _update_pickable(); + if (disabled && (disable_mode != DISABLE_MODE_REMOVE)) { + _apply_disabled(); + } + + if (!disabled || (disable_mode != DISABLE_MODE_REMOVE)) { + RID space = get_world_2d()->get_space(); + if (area) { + PhysicsServer2D::get_singleton()->area_set_space(rid, space); + } else { + PhysicsServer2D::get_singleton()->body_set_space(rid, space); + } + } - //get space + _update_pickable(); } break; case NOTIFICATION_ENTER_CANVAS: { - - if (area) + if (area) { PhysicsServer2D::get_singleton()->area_attach_canvas_instance_id(rid, get_canvas_layer_instance_id()); - else + } else { PhysicsServer2D::get_singleton()->body_attach_canvas_instance_id(rid, get_canvas_layer_instance_id()); + } } break; case NOTIFICATION_VISIBILITY_CHANGED: { - _update_pickable(); } break; - case NOTIFICATION_TRANSFORM_CHANGED: { + case NOTIFICATION_TRANSFORM_CHANGED: { if (only_update_transform_changes) { return; } Transform2D global_transform = get_global_transform(); - if (area) + if (area) { PhysicsServer2D::get_singleton()->area_set_transform(rid, global_transform); - else + } else { PhysicsServer2D::get_singleton()->body_set_state(rid, PhysicsServer2D::BODY_STATE_TRANSFORM, global_transform); - + } } break; - case NOTIFICATION_EXIT_TREE: { - if (area) { - PhysicsServer2D::get_singleton()->area_set_space(rid, RID()); - } else - PhysicsServer2D::get_singleton()->body_set_space(rid, RID()); + case NOTIFICATION_EXIT_TREE: { + bool disabled = !is_enabled(); + + if (!disabled || (disable_mode != DISABLE_MODE_REMOVE)) { + if (area) { + PhysicsServer2D::get_singleton()->area_set_space(rid, RID()); + } else { + PhysicsServer2D::get_singleton()->body_set_space(rid, RID()); + } + } + if (disabled && (disable_mode != DISABLE_MODE_REMOVE)) { + _apply_enabled(); + } } break; case NOTIFICATION_EXIT_CANVAS: { - - if (area) + if (area) { PhysicsServer2D::get_singleton()->area_attach_canvas_instance_id(rid, ObjectID()); - else + } else { PhysicsServer2D::get_singleton()->body_attach_canvas_instance_id(rid, ObjectID()); + } + } break; + + case NOTIFICATION_DISABLED: { + _apply_disabled(); + } break; + + case NOTIFICATION_ENABLED: { + _apply_enabled(); } break; } } -uint32_t CollisionObject2D::create_shape_owner(Object *p_owner) { +void CollisionObject2D::set_collision_layer(uint32_t p_layer) { + collision_layer = p_layer; + if (area) { + PhysicsServer2D::get_singleton()->area_set_collision_layer(get_rid(), p_layer); + } else { + PhysicsServer2D::get_singleton()->body_set_collision_layer(get_rid(), p_layer); + } +} + +uint32_t CollisionObject2D::get_collision_layer() const { + return collision_layer; +} + +void CollisionObject2D::set_collision_mask(uint32_t p_mask) { + collision_mask = p_mask; + if (area) { + PhysicsServer2D::get_singleton()->area_set_collision_mask(get_rid(), p_mask); + } else { + PhysicsServer2D::get_singleton()->body_set_collision_mask(get_rid(), p_mask); + } +} + +uint32_t CollisionObject2D::get_collision_mask() const { + return collision_mask; +} + +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_layer_number - 1); + } else { + collision_layer &= ~(1 << (p_layer_number - 1)); + } + set_collision_layer(collision_layer); +} + +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_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive."); + uint32_t mask = get_collision_mask(); + if (p_value) { + mask |= 1 << (p_layer_number - 1); + } else { + mask &= ~(1 << (p_layer_number - 1)); + } + set_collision_mask(mask); +} + +bool 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) { + if (disable_mode == p_mode) { + return; + } + + bool disabled = is_inside_tree() && !is_enabled(); + + if (disabled) { + // Cancel previous disable mode. + _apply_enabled(); + } + + disable_mode = p_mode; + + if (disabled) { + // Apply new disable mode. + _apply_disabled(); + } +} + +CollisionObject2D::DisableMode CollisionObject2D::get_disable_mode() const { + return disable_mode; +} + +void CollisionObject2D::_apply_disabled() { + switch (disable_mode) { + case DISABLE_MODE_REMOVE: { + if (is_inside_tree()) { + if (area) { + PhysicsServer2D::get_singleton()->area_set_space(rid, RID()); + } else { + PhysicsServer2D::get_singleton()->body_set_space(rid, RID()); + } + } + } break; + + case DISABLE_MODE_MAKE_STATIC: { + if (!area && (body_mode != PhysicsServer2D::BODY_MODE_STATIC)) { + PhysicsServer2D::get_singleton()->body_set_mode(rid, PhysicsServer2D::BODY_MODE_STATIC); + } + } break; + + case DISABLE_MODE_KEEP_ACTIVE: { + // Nothing to do. + } break; + } +} +void CollisionObject2D::_apply_enabled() { + switch (disable_mode) { + case DISABLE_MODE_REMOVE: { + if (is_inside_tree()) { + RID space = get_world_2d()->get_space(); + if (area) { + PhysicsServer2D::get_singleton()->area_set_space(rid, space); + } else { + PhysicsServer2D::get_singleton()->body_set_space(rid, space); + } + } + } break; + + case DISABLE_MODE_MAKE_STATIC: { + if (!area && (body_mode != PhysicsServer2D::BODY_MODE_STATIC)) { + PhysicsServer2D::get_singleton()->body_set_mode(rid, body_mode); + } + } break; + + case DISABLE_MODE_KEEP_ACTIVE: { + // Nothing to do. + } break; + } +} + +uint32_t CollisionObject2D::create_shape_owner(Object *p_owner) { ShapeData sd; uint32_t id; @@ -121,7 +274,6 @@ uint32_t CollisionObject2D::create_shape_owner(Object *p_owner) { } void CollisionObject2D::remove_shape_owner(uint32_t owner) { - ERR_FAIL_COND(!shapes.has(owner)); shape_owner_clear_shapes(owner); @@ -144,16 +296,15 @@ void CollisionObject2D::shape_owner_set_disabled(uint32_t p_owner, bool p_disabl } bool CollisionObject2D::is_shape_owner_disabled(uint32_t p_owner) const { - ERR_FAIL_COND_V(!shapes.has(p_owner), false); return shapes[p_owner].disabled; } void CollisionObject2D::shape_owner_set_one_way_collision(uint32_t p_owner, bool p_enable) { - - if (area) + if (area) { return; //not for areas + } ERR_FAIL_COND(!shapes.has(p_owner)); @@ -165,16 +316,15 @@ void CollisionObject2D::shape_owner_set_one_way_collision(uint32_t p_owner, bool } bool CollisionObject2D::is_shape_owner_one_way_collision_enabled(uint32_t p_owner) const { - ERR_FAIL_COND_V(!shapes.has(p_owner), false); return shapes[p_owner].one_way_collision; } -void CollisionObject2D::shape_owner_set_one_way_collision_margin(uint32_t p_owner, float p_margin) { - - if (area) +void CollisionObject2D::shape_owner_set_one_way_collision_margin(uint32_t p_owner, real_t p_margin) { + if (area) { return; //not for areas + } ERR_FAIL_COND(!shapes.has(p_owner)); @@ -185,22 +335,19 @@ void CollisionObject2D::shape_owner_set_one_way_collision_margin(uint32_t p_owne } } -float CollisionObject2D::get_shape_owner_one_way_collision_margin(uint32_t p_owner) const { - +real_t CollisionObject2D::get_shape_owner_one_way_collision_margin(uint32_t p_owner) const { ERR_FAIL_COND_V(!shapes.has(p_owner), 0); return shapes[p_owner].one_way_collision_margin; } void CollisionObject2D::get_shape_owners(List<uint32_t> *r_owners) { - for (Map<uint32_t, ShapeData>::Element *E = shapes.front(); E; E = E->next()) { r_owners->push_back(E->key()); } } Array CollisionObject2D::_get_shape_owners() { - Array ret; for (Map<uint32_t, ShapeData>::Element *E = shapes.front(); E; E = E->next()) { ret.push_back(E->key()); @@ -210,7 +357,6 @@ Array CollisionObject2D::_get_shape_owners() { } void CollisionObject2D::shape_owner_set_transform(uint32_t p_owner, const Transform2D &p_transform) { - ERR_FAIL_COND(!shapes.has(p_owner)); ShapeData &sd = shapes[p_owner]; @@ -224,22 +370,20 @@ void CollisionObject2D::shape_owner_set_transform(uint32_t p_owner, const Transf } } } -Transform2D CollisionObject2D::shape_owner_get_transform(uint32_t p_owner) const { +Transform2D CollisionObject2D::shape_owner_get_transform(uint32_t p_owner) const { ERR_FAIL_COND_V(!shapes.has(p_owner), Transform2D()); return shapes[p_owner].xform; } Object *CollisionObject2D::shape_owner_get_owner(uint32_t p_owner) const { - ERR_FAIL_COND_V(!shapes.has(p_owner), nullptr); return shapes[p_owner].owner; } void CollisionObject2D::shape_owner_add_shape(uint32_t p_owner, const Ref<Shape2D> &p_shape) { - ERR_FAIL_COND(!shapes.has(p_owner)); ERR_FAIL_COND(p_shape.is_null()); @@ -256,21 +400,21 @@ void CollisionObject2D::shape_owner_add_shape(uint32_t p_owner, const Ref<Shape2 total_subshapes++; } -int CollisionObject2D::shape_owner_get_shape_count(uint32_t p_owner) const { +int CollisionObject2D::shape_owner_get_shape_count(uint32_t p_owner) const { ERR_FAIL_COND_V(!shapes.has(p_owner), 0); return shapes[p_owner].shapes.size(); } -Ref<Shape2D> CollisionObject2D::shape_owner_get_shape(uint32_t p_owner, int p_shape) const { +Ref<Shape2D> CollisionObject2D::shape_owner_get_shape(uint32_t p_owner, int p_shape) const { ERR_FAIL_COND_V(!shapes.has(p_owner), Ref<Shape2D>()); ERR_FAIL_INDEX_V(p_shape, shapes[p_owner].shapes.size(), Ref<Shape2D>()); return shapes[p_owner].shapes[p_shape].shape; } -int CollisionObject2D::shape_owner_get_shape_index(uint32_t p_owner, int p_shape) const { +int CollisionObject2D::shape_owner_get_shape_index(uint32_t p_owner, int p_shape) const { ERR_FAIL_COND_V(!shapes.has(p_owner), -1); ERR_FAIL_INDEX_V(p_shape, shapes[p_owner].shapes.size(), -1); @@ -278,7 +422,6 @@ int CollisionObject2D::shape_owner_get_shape_index(uint32_t p_owner, int p_shape } void CollisionObject2D::shape_owner_remove_shape(uint32_t p_owner, int p_shape) { - ERR_FAIL_COND(!shapes.has(p_owner)); ERR_FAIL_INDEX(p_shape, shapes[p_owner].shapes.size()); @@ -303,7 +446,6 @@ void CollisionObject2D::shape_owner_remove_shape(uint32_t p_owner, int p_shape) } void CollisionObject2D::shape_owner_clear_shapes(uint32_t p_owner) { - ERR_FAIL_COND(!shapes.has(p_owner)); while (shape_owner_get_shape_count(p_owner) > 0) { @@ -312,7 +454,6 @@ void CollisionObject2D::shape_owner_clear_shapes(uint32_t p_owner) { } uint32_t CollisionObject2D::shape_find_owner(int p_shape_index) const { - ERR_FAIL_INDEX_V(p_shape_index, total_subshapes, 0); for (const Map<uint32_t, ShapeData>::Element *E = shapes.front(); E; E = E->next()) { @@ -328,29 +469,24 @@ uint32_t CollisionObject2D::shape_find_owner(int p_shape_index) const { } void CollisionObject2D::set_pickable(bool p_enabled) { - - if (pickable == p_enabled) + if (pickable == p_enabled) { return; + } pickable = p_enabled; _update_pickable(); } 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); } void CollisionObject2D::_mouse_enter() { - if (get_script_instance()) { get_script_instance()->call(SceneStringNames::get_singleton()->_mouse_enter); } @@ -358,46 +494,85 @@ void CollisionObject2D::_mouse_enter() { } void CollisionObject2D::_mouse_exit() { - if (get_script_instance()) { get_script_instance()->call(SceneStringNames::get_singleton()->_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; } +bool CollisionObject2D::is_only_update_transform_changes_enabled() const { + return only_update_transform_changes; +} + +void CollisionObject2D::set_body_mode(PhysicsServer2D::BodyMode p_mode) { + ERR_FAIL_COND(area); + + if (body_mode == p_mode) { + return; + } + + body_mode = p_mode; + + if (is_inside_tree() && !is_enabled() && (disable_mode == DISABLE_MODE_MAKE_STATIC)) { + return; + } + + PhysicsServer2D::get_singleton()->body_set_mode(rid, p_mode); +} + void CollisionObject2D::_update_pickable() { - if (!is_inside_tree()) + if (!is_inside_tree()) { return; + } bool is_pickable = pickable && is_visible_in_tree(); - if (area) + if (area) { PhysicsServer2D::get_singleton()->area_set_pickable(rid, is_pickable); - else + } else { PhysicsServer2D::get_singleton()->body_set_pickable(rid, is_pickable); + } } -String CollisionObject2D::get_configuration_warning() const { - - String warning = Node2D::get_configuration_warning(); +TypedArray<String> CollisionObject2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); - if (shapes.empty()) { - if (!warning.empty()) { - warning += "\n\n"; - } - warning += TTR("This node has no shape, so it can't collide or interact with other objects.\nConsider adding a CollisionShape2D or CollisionPolygon2D as a child to define its shape."); + if (shapes.is_empty()) { + warnings.push_back(TTR("This node has no shape, so it can't collide or interact with other objects.\nConsider adding a CollisionShape2D or CollisionPolygon2D as a child to define its shape.")); } - return warning; + return warnings; } void CollisionObject2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_rid"), &CollisionObject2D::get_rid); - + ClassDB::bind_method(D_METHOD("set_collision_layer", "layer"), &CollisionObject2D::set_collision_layer); + 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_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); ClassDB::bind_method(D_METHOD("is_pickable"), &CollisionObject2D::is_pickable); ClassDB::bind_method(D_METHOD("create_shape_owner", "owner"), &CollisionObject2D::create_shape_owner); @@ -420,19 +595,29 @@ 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"); + + ADD_GROUP("Collision", "collision_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_layer", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_layer", "get_collision_layer"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_mask", "get_collision_mask"); - ADD_GROUP("Pickable", "input_"); + ADD_GROUP("Input", "input_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "input_pickable"), "set_pickable", "is_pickable"); - ADD_GROUP("", ""); + + BIND_ENUM_CONSTANT(DISABLE_MODE_REMOVE); + BIND_ENUM_CONSTANT(DISABLE_MODE_MAKE_STATIC); + BIND_ENUM_CONSTANT(DISABLE_MODE_KEEP_ACTIVE); } CollisionObject2D::CollisionObject2D(RID p_rid, bool p_area) { - rid = p_rid; area = p_area; pickable = true; @@ -441,21 +626,19 @@ CollisionObject2D::CollisionObject2D(RID p_rid, bool p_area) { only_update_transform_changes = false; if (p_area) { - PhysicsServer2D::get_singleton()->area_attach_object_instance_id(rid, get_instance_id()); } else { PhysicsServer2D::get_singleton()->body_attach_object_instance_id(rid, get_instance_id()); + PhysicsServer2D::get_singleton()->body_set_mode(rid, body_mode); } } CollisionObject2D::CollisionObject2D() { - //owner= set_notify_transform(true); } CollisionObject2D::~CollisionObject2D() { - PhysicsServer2D::get_singleton()->free(rid); } diff --git a/scene/2d/collision_object_2d.h b/scene/2d/collision_object_2d.h index e931f20f40..19abacb201 100644 --- a/scene/2d/collision_object_2d.h +++ b/scene/2d/collision_object_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,42 +32,54 @@ #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" class CollisionObject2D : public Node2D { - GDCLASS(CollisionObject2D, Node2D); - bool area; +public: + enum DisableMode { + DISABLE_MODE_REMOVE, + DISABLE_MODE_MAKE_STATIC, + DISABLE_MODE_KEEP_ACTIVE, + }; + +private: + uint32_t collision_layer = 1; + uint32_t collision_mask = 1; + + bool area = false; RID rid; - bool pickable; + bool pickable = false; - struct ShapeData { + DisableMode disable_mode = DISABLE_MODE_REMOVE; + + PhysicsServer2D::BodyMode body_mode = PhysicsServer2D::BODY_MODE_STATIC; - Object *owner; + struct ShapeData { + Object *owner = nullptr; Transform2D xform; struct Shape { Ref<Shape2D> shape; - int index; + int index = 0; }; Vector<Shape> shapes; - bool disabled; - bool one_way_collision; - float one_way_collision_margin; - - ShapeData() { - disabled = false; - one_way_collision = false; - one_way_collision_margin = 0; - owner = nullptr; - } + + bool disabled = false; + bool one_way_collision = false; + real_t one_way_collision_margin = 0.0; }; - int total_subshapes; + int total_subshapes = 0; Map<uint32_t, ShapeData> shapes; - bool only_update_transform_changes; //this is used for sync physics in KinematicBody + bool only_update_transform_changes = false; // This is used for sync to physics. + + void _apply_disabled(); + void _apply_enabled(); protected: CollisionObject2D(RID p_rid, bool p_area); @@ -77,13 +89,35 @@ 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; + + void set_collision_mask(uint32_t p_mask); + uint32_t get_collision_mask() 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_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; + uint32_t create_shape_owner(Object *p_owner); void remove_shape_owner(uint32_t owner); void get_shape_owners(List<uint32_t> *r_owners); @@ -99,8 +133,8 @@ public: void shape_owner_set_one_way_collision(uint32_t p_owner, bool p_enable); bool is_shape_owner_one_way_collision_enabled(uint32_t p_owner) const; - void shape_owner_set_one_way_collision_margin(uint32_t p_owner, float p_margin); - float get_shape_owner_one_way_collision_margin(uint32_t p_owner) const; + void shape_owner_set_one_way_collision_margin(uint32_t p_owner, real_t p_margin); + real_t get_shape_owner_one_way_collision_margin(uint32_t p_owner) const; void shape_owner_add_shape(uint32_t p_owner, const Ref<Shape2D> &p_shape); int shape_owner_get_shape_count(uint32_t p_owner) const; @@ -115,7 +149,7 @@ public: void set_pickable(bool p_enabled); bool is_pickable() const; - String get_configuration_warning() const; + TypedArray<String> get_configuration_warnings() const override; _FORCE_INLINE_ RID get_rid() const { return rid; } @@ -123,4 +157,6 @@ public: ~CollisionObject2D(); }; +VARIANT_ENUM_CAST(CollisionObject2D::DisableMode); + #endif // COLLISION_OBJECT_2D_H diff --git a/scene/2d/collision_polygon_2d.cpp b/scene/2d/collision_polygon_2d.cpp index 1e48b2d67f..8c8a292ad7 100644 --- a/scene/2d/collision_polygon_2d.cpp +++ b/scene/2d/collision_polygon_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,22 +31,21 @@ #include "collision_polygon_2d.h" #include "collision_object_2d.h" -#include "core/engine.h" +#include "core/math/geometry_2d.h" #include "scene/resources/concave_polygon_shape_2d.h" #include "scene/resources/convex_polygon_shape_2d.h" -#include "thirdparty/misc/triangulator.h" +#include "thirdparty/misc/polypartition.h" void CollisionPolygon2D::_build_polygon() { - parent->shape_owner_clear_shapes(owner_id); - if (polygon.size() == 0) - return; - bool solids = build_mode == BUILD_SOLIDS; if (solids) { + if (polygon.size() < 3) { + return; + } //here comes the sun, lalalala //decompose concave into multiple convex polygons and add them @@ -58,6 +57,9 @@ void CollisionPolygon2D::_build_polygon() { } } else { + if (polygon.size() < 2) { + return; + } Ref<ConcavePolygonShape2D> concave = memnew(ConcavePolygonShape2D); @@ -77,25 +79,23 @@ void CollisionPolygon2D::_build_polygon() { } Vector<Vector<Vector2>> CollisionPolygon2D::_decompose_in_convex() { - Vector<Vector<Vector2>> decomp = Geometry::decompose_polygon_in_convex(polygon); + Vector<Vector<Vector2>> decomp = Geometry2D::decompose_polygon_in_convex(polygon); return decomp; } void CollisionPolygon2D::_update_in_shape_owner(bool p_xform_only) { - parent->shape_owner_set_transform(owner_id, get_transform()); - if (p_xform_only) + if (p_xform_only) { return; + } parent->shape_owner_set_disabled(owner_id, disabled); parent->shape_owner_set_one_way_collision(owner_id, one_way_collision); parent->shape_owner_set_one_way_collision_margin(owner_id, one_way_collision_margin); } void CollisionPolygon2D::_notification(int p_what) { - switch (p_what) { case NOTIFICATION_PARENTED: { - parent = Object::cast_to<CollisionObject2D>(get_parent()); if (parent) { owner_id = parent->create_shape_owner(this); @@ -111,14 +111,12 @@ void CollisionPolygon2D::_notification(int p_what) { } break; case NOTIFICATION_ENTER_TREE: { - if (parent) { _update_in_shape_owner(); } } break; case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { - if (parent) { _update_in_shape_owner(true); } @@ -133,32 +131,32 @@ void CollisionPolygon2D::_notification(int p_what) { } break; case NOTIFICATION_DRAW: { - if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) { break; } - for (int i = 0; i < polygon.size(); i++) { - + int polygon_count = polygon.size(); + for (int i = 0; i < polygon_count; i++) { Vector2 p = polygon[i]; - Vector2 n = polygon[(i + 1) % polygon.size()]; + Vector2 n = polygon[(i + 1) % polygon_count]; // draw line with width <= 1, so it does not scale with zoom and break pixel exact editing draw_line(p, n, Color(0.9, 0.2, 0.0, 0.8), 1); } + + if (polygon_count > 2) { #define DEBUG_DECOMPOSE #if defined(TOOLS_ENABLED) && defined(DEBUG_DECOMPOSE) + Vector<Vector<Vector2>> decomp = _decompose_in_convex(); - Vector<Vector<Vector2>> decomp = _decompose_in_convex(); - - Color c(0.4, 0.9, 0.1); - for (int i = 0; i < decomp.size(); i++) { - - c.set_hsv(Math::fmod(c.get_h() + 0.738, 1), c.get_s(), c.get_v(), 0.5); - draw_colored_polygon(decomp[i], c); - } + Color c(0.4, 0.9, 0.1); + for (int i = 0; i < decomp.size(); i++) { + c.set_hsv(Math::fmod(c.get_h() + 0.738, 1), c.get_s(), c.get_v(), 0.5); + draw_colored_polygon(decomp[i], c); + } #else - draw_colored_polygon(polygon, get_tree()->get_debug_collisions_color()); + draw_colored_polygon(polygon, get_tree()->get_debug_collisions_color()); #endif + } if (one_way_collision) { Color dcol = get_tree()->get_debug_collisions_color(); //0.9,0.2,0.2,0.4); @@ -166,13 +164,14 @@ void CollisionPolygon2D::_notification(int p_what) { Vector2 line_to(0, 20); draw_line(Vector2(), line_to, dcol, 3); Vector<Vector2> pts; - float tsize = 8; + real_t tsize = 8; pts.push_back(line_to + (Vector2(0, tsize))); pts.push_back(line_to + (Vector2(Math_SQRT12 * tsize, 0))); pts.push_back(line_to + (Vector2(-Math_SQRT12 * tsize, 0))); Vector<Color> cols; - for (int i = 0; i < 3; i++) + for (int i = 0; i < 3; i++) { cols.push_back(dcol); + } draw_primitive(pts, cols, Vector<Vector2>()); //small arrow } @@ -181,18 +180,17 @@ void CollisionPolygon2D::_notification(int p_what) { } void CollisionPolygon2D::set_polygon(const Vector<Point2> &p_polygon) { - polygon = p_polygon; { for (int i = 0; i < polygon.size(); i++) { - if (i == 0) + if (i == 0) { aabb = Rect2(polygon[i], Size2()); - else + } else { aabb.expand_to(polygon[i]); + } } if (aabb == Rect2()) { - aabb = Rect2(-10, -10, 20, 20); } else { aabb.position -= aabb.size * 0.3; @@ -202,33 +200,33 @@ void CollisionPolygon2D::set_polygon(const Vector<Point2> &p_polygon) { if (parent) { _build_polygon(); + _update_in_shape_owner(); } update(); - update_configuration_warning(); + update_configuration_warnings(); } Vector<Point2> CollisionPolygon2D::get_polygon() const { - return polygon; } void CollisionPolygon2D::set_build_mode(BuildMode p_mode) { - ERR_FAIL_INDEX((int)p_mode, 2); build_mode = p_mode; if (parent) { _build_polygon(); + _update_in_shape_owner(); } + update(); + update_configuration_warnings(); } CollisionPolygon2D::BuildMode CollisionPolygon2D::get_build_mode() const { - return build_mode; } #ifdef TOOLS_ENABLED Rect2 CollisionPolygon2D::_edit_get_rect() const { - return aabb; } @@ -237,22 +235,32 @@ bool CollisionPolygon2D::_edit_use_rect() const { } bool CollisionPolygon2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { - - return Geometry::is_point_in_polygon(p_point, Variant(polygon)); + return Geometry2D::is_point_in_polygon(p_point, Variant(polygon)); } #endif -String CollisionPolygon2D::get_configuration_warning() const { +TypedArray<String> CollisionPolygon2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); if (!Object::cast_to<CollisionObject2D>(get_parent())) { - return TTR("CollisionPolygon2D only serves to provide a collision shape to a CollisionObject2D derived node. Please only use it as a child of Area2D, StaticBody2D, RigidBody2D, KinematicBody2D, etc. to give them a shape."); + warnings.push_back(TTR("CollisionPolygon2D only serves to provide a collision shape to a CollisionObject2D derived node. Please only use it as a child of Area2D, StaticBody2D, RigidBody2D, CharacterBody2D, etc. to give them a shape.")); } - if (polygon.empty()) { - return TTR("An empty CollisionPolygon2D has no effect on collision."); + int polygon_count = polygon.size(); + if (polygon_count == 0) { + warnings.push_back(TTR("An empty CollisionPolygon2D has no effect on collision.")); + } else { + bool solids = build_mode == BUILD_SOLIDS; + if (solids) { + if (polygon_count < 3) { + warnings.push_back(TTR("Invalid polygon. At least 3 points are needed in 'Solids' build mode.")); + } + } else if (polygon_count < 2) { + warnings.push_back(TTR("Invalid polygon. At least 2 points are needed in 'Segments' build mode.")); + } } - return String(); + return warnings; } void CollisionPolygon2D::set_disabled(bool p_disabled) { @@ -276,22 +284,21 @@ void CollisionPolygon2D::set_one_way_collision(bool p_enable) { } bool CollisionPolygon2D::is_one_way_collision_enabled() const { - return one_way_collision; } -void CollisionPolygon2D::set_one_way_collision_margin(float p_margin) { +void CollisionPolygon2D::set_one_way_collision_margin(real_t p_margin) { one_way_collision_margin = p_margin; if (parent) { parent->shape_owner_set_one_way_collision_margin(owner_id, one_way_collision_margin); } } -float CollisionPolygon2D::get_one_way_collision_margin() const { +real_t CollisionPolygon2D::get_one_way_collision_margin() const { return one_way_collision_margin; } -void CollisionPolygon2D::_bind_methods() { +void CollisionPolygon2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_polygon", "polygon"), &CollisionPolygon2D::set_polygon); ClassDB::bind_method(D_METHOD("get_polygon"), &CollisionPolygon2D::get_polygon); @@ -315,13 +322,5 @@ void CollisionPolygon2D::_bind_methods() { } CollisionPolygon2D::CollisionPolygon2D() { - - aabb = Rect2(-10, -10, 20, 20); - build_mode = BUILD_SOLIDS; set_notify_local_transform(true); - parent = nullptr; - owner_id = 0; - disabled = false; - one_way_collision = false; - one_way_collision_margin = 1.0; } diff --git a/scene/2d/collision_polygon_2d.h b/scene/2d/collision_polygon_2d.h index 92a2758813..6b32923010 100644 --- a/scene/2d/collision_polygon_2d.h +++ b/scene/2d/collision_polygon_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,12 +32,10 @@ #define COLLISION_POLYGON_2D_H #include "scene/2d/node_2d.h" -#include "scene/resources/shape_2d.h" class CollisionObject2D; class CollisionPolygon2D : public Node2D { - GDCLASS(CollisionPolygon2D, Node2D); public: @@ -47,14 +45,14 @@ public: }; protected: - Rect2 aabb; - BuildMode build_mode; + Rect2 aabb = Rect2(-10, -10, 20, 20); + BuildMode build_mode = BUILD_SOLIDS; Vector<Point2> polygon; - uint32_t owner_id; - CollisionObject2D *parent; - bool disabled; - bool one_way_collision; - float one_way_collision_margin; + uint32_t owner_id = 0; + CollisionObject2D *parent = nullptr; + bool disabled = false; + bool one_way_collision = false; + real_t one_way_collision_margin = 1.0; Vector<Vector<Vector2>> _decompose_in_convex(); @@ -68,9 +66,9 @@ protected: public: #ifdef TOOLS_ENABLED - virtual Rect2 _edit_get_rect() const; - virtual bool _edit_use_rect() const; - virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const; + virtual Rect2 _edit_get_rect() const override; + virtual bool _edit_use_rect() const override; + virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override; #endif void set_build_mode(BuildMode p_mode); @@ -79,7 +77,7 @@ public: void set_polygon(const Vector<Point2> &p_polygon); Vector<Point2> get_polygon() const; - virtual String get_configuration_warning() const; + TypedArray<String> get_configuration_warnings() const override; void set_disabled(bool p_disabled); bool is_disabled() const; @@ -87,8 +85,8 @@ public: void set_one_way_collision(bool p_enable); bool is_one_way_collision_enabled() const; - void set_one_way_collision_margin(float p_margin); - float get_one_way_collision_margin() const; + void set_one_way_collision_margin(real_t p_margin); + real_t get_one_way_collision_margin() const; CollisionPolygon2D(); }; diff --git a/scene/2d/collision_shape_2d.cpp b/scene/2d/collision_shape_2d.cpp index b1dbc57c94..d52795f0d5 100644 --- a/scene/2d/collision_shape_2d.cpp +++ b/scene/2d/collision_shape_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,36 +31,26 @@ #include "collision_shape_2d.h" #include "collision_object_2d.h" -#include "core/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(); } void CollisionShape2D::_update_in_shape_owner(bool p_xform_only) { - parent->shape_owner_set_transform(owner_id, get_transform()); - if (p_xform_only) + if (p_xform_only) { return; + } parent->shape_owner_set_disabled(owner_id, disabled); parent->shape_owner_set_one_way_collision(owner_id, one_way_collision); parent->shape_owner_set_one_way_collision_margin(owner_id, one_way_collision_margin); } void CollisionShape2D::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_PARENTED: { - parent = Object::cast_to<CollisionObject2D>(get_parent()); if (parent) { owner_id = parent->create_shape_owner(this); @@ -78,14 +68,12 @@ void CollisionShape2D::_notification(int p_what) { } break; case NOTIFICATION_ENTER_TREE: { - if (parent) { _update_in_shape_owner(); } } break; case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { - if (parent) { _update_in_shape_owner(true); } @@ -100,7 +88,6 @@ void CollisionShape2D::_notification(int p_what) { } break; case NOTIFICATION_DRAW: { - if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) { break; } @@ -117,6 +104,7 @@ void CollisionShape2D::_notification(int p_what) { draw_col.r = g; draw_col.g = g; draw_col.b = g; + draw_col.a *= 0.5; } shape->draw(get_canvas_item(), draw_col); @@ -132,13 +120,14 @@ void CollisionShape2D::_notification(int p_what) { Vector2 line_to(0, 20); draw_line(Vector2(), line_to, draw_col, 2); Vector<Vector2> pts; - float tsize = 8; + real_t tsize = 8; pts.push_back(line_to + (Vector2(0, tsize))); pts.push_back(line_to + (Vector2(Math_SQRT12 * tsize, 0))); pts.push_back(line_to + (Vector2(-Math_SQRT12 * tsize, 0))); Vector<Color> cols; - for (int i = 0; i < 3; i++) + for (int i = 0; i < 3; i++) { cols.push_back(draw_col); + } draw_primitive(pts, cols, Vector<Vector2>()); } @@ -147,9 +136,12 @@ void CollisionShape2D::_notification(int p_what) { } void CollisionShape2D::set_shape(const Ref<Shape2D> &p_shape) { - - if (shape.is_valid()) + if (p_shape == shape) { + return; + } + if (shape.is_valid()) { shape->disconnect("changed", callable_mp(this, &CollisionShape2D::_shape_changed)); + } shape = p_shape; update(); if (parent) { @@ -157,38 +149,45 @@ void CollisionShape2D::set_shape(const Ref<Shape2D> &p_shape) { if (shape.is_valid()) { parent->shape_owner_add_shape(owner_id, shape); } + _update_in_shape_owner(); } - if (shape.is_valid()) + if (shape.is_valid()) { shape->connect("changed", callable_mp(this, &CollisionShape2D::_shape_changed)); + } - update_configuration_warning(); + update_configuration_warnings(); } Ref<Shape2D> CollisionShape2D::get_shape() const { - return shape; } bool CollisionShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { - - if (!shape.is_valid()) + if (!shape.is_valid()) { return false; + } return shape->_edit_is_selected_on_click(p_point, p_tolerance); } -String CollisionShape2D::get_configuration_warning() const { +TypedArray<String> CollisionShape2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); if (!Object::cast_to<CollisionObject2D>(get_parent())) { - return TTR("CollisionShape2D only serves to provide a collision shape to a CollisionObject2D derived node. Please only use it as a child of Area2D, StaticBody2D, RigidBody2D, KinematicBody2D, etc. to give them a shape."); + warnings.push_back(TTR("CollisionShape2D only serves to provide a collision shape to a CollisionObject2D derived node. Please only use it as a child of Area2D, StaticBody2D, RigidBody2D, CharacterBody2D, etc. to give them a shape.")); } - if (!shape.is_valid()) { - return TTR("A shape must be provided for CollisionShape2D to function. Please create a shape resource for it!"); + warnings.push_back(TTR("A shape must be provided for CollisionShape2D to function. Please create a shape resource for it!")); + } + + Ref<ConvexPolygonShape2D> convex = shape; + Ref<ConcavePolygonShape2D> concave = shape; + if (convex.is_valid() || concave.is_valid()) { + warnings.push_back(TTR("Polygon-based shapes are not meant be used nor edited directly through the CollisionShape2D node. Please use the CollisionPolygon2D node instead.")); } - return String(); + return warnings; } void CollisionShape2D::set_disabled(bool p_disabled) { @@ -212,23 +211,21 @@ void CollisionShape2D::set_one_way_collision(bool p_enable) { } bool CollisionShape2D::is_one_way_collision_enabled() const { - return one_way_collision; } -void CollisionShape2D::set_one_way_collision_margin(float p_margin) { +void CollisionShape2D::set_one_way_collision_margin(real_t p_margin) { one_way_collision_margin = p_margin; if (parent) { parent->shape_owner_set_one_way_collision_margin(owner_id, one_way_collision_margin); } } -float CollisionShape2D::get_one_way_collision_margin() const { +real_t CollisionShape2D::get_one_way_collision_margin() const { return one_way_collision_margin; } void CollisionShape2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_shape", "shape"), &CollisionShape2D::set_shape); ClassDB::bind_method(D_METHOD("get_shape"), &CollisionShape2D::get_shape); ClassDB::bind_method(D_METHOD("set_disabled", "disabled"), &CollisionShape2D::set_disabled); @@ -245,12 +242,5 @@ void CollisionShape2D::_bind_methods() { } CollisionShape2D::CollisionShape2D() { - - rect = Rect2(-Point2(10, 10), Point2(20, 20)); set_notify_local_transform(true); - owner_id = 0; - parent = nullptr; - disabled = false; - one_way_collision = false; - one_way_collision_margin = 1.0; } diff --git a/scene/2d/collision_shape_2d.h b/scene/2d/collision_shape_2d.h index 80bea0a979..eaf72627c8 100644 --- a/scene/2d/collision_shape_2d.h +++ b/scene/2d/collision_shape_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -37,16 +37,15 @@ class CollisionObject2D; class CollisionShape2D : public Node2D { - GDCLASS(CollisionShape2D, Node2D); Ref<Shape2D> shape; - Rect2 rect; - uint32_t owner_id; - CollisionObject2D *parent; + Rect2 rect = Rect2(-Point2(10, 10), Point2(20, 20)); + uint32_t owner_id = 0; + CollisionObject2D *parent = nullptr; void _shape_changed(); - bool disabled; - bool one_way_collision; - float one_way_collision_margin; + bool disabled = false; + bool one_way_collision = false; + real_t one_way_collision_margin = 1.0; void _update_in_shape_owner(bool p_xform_only = false); @@ -55,7 +54,11 @@ protected: static void _bind_methods(); public: +#ifdef TOOLS_ENABLED + virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override; +#else virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const; +#endif // TOOLS_ENABLED void set_shape(const Ref<Shape2D> &p_shape); Ref<Shape2D> get_shape() const; @@ -66,10 +69,10 @@ public: void set_one_way_collision(bool p_enable); bool is_one_way_collision_enabled() const; - void set_one_way_collision_margin(float p_margin); - float get_one_way_collision_margin() const; + void set_one_way_collision_margin(real_t p_margin); + real_t get_one_way_collision_margin() const; - virtual String get_configuration_warning() const; + TypedArray<String> get_configuration_warnings() const override; CollisionShape2D(); }; diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp index 0a6b091a51..b836497627 100644 --- a/scene/2d/cpu_particles_2d.cpp +++ b/scene/2d/cpu_particles_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,22 +32,20 @@ #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) + if (emitting == p_emitting) { return; + } emitting = p_emitting; - if (emitting) + if (emitting) { set_process_internal(true); + } } void CPUParticles2D::set_amount(int p_amount) { - ERR_FAIL_COND_MSG(p_amount < 1, "Amount of particles must be greater than 0."); particles.resize(p_amount); @@ -60,104 +58,94 @@ void CPUParticles2D::set_amount(int p_amount) { } particle_data.resize((8 + 4 + 4) * p_amount); - RS::get_singleton()->multimesh_allocate(multimesh, p_amount, RS::MULTIMESH_TRANSFORM_2D, true, true); + RS::get_singleton()->multimesh_allocate_data(multimesh, p_amount, RS::MULTIMESH_TRANSFORM_2D, true, true); 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; } 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; } -void CPUParticles2D::set_explosiveness_ratio(float p_ratio) { +void CPUParticles2D::set_explosiveness_ratio(real_t p_ratio) { explosiveness_ratio = p_ratio; } -void CPUParticles2D::set_randomness_ratio(float p_ratio) { +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; } -void CPUParticles2D::set_use_local_coordinates(bool p_enable) { +void CPUParticles2D::set_use_local_coordinates(bool p_enable) { local_coords = p_enable; set_notify_transform(!p_enable); } -void CPUParticles2D::set_speed_scale(float p_scale) { - +void CPUParticles2D::set_speed_scale(double p_scale) { speed_scale = p_scale; } bool CPUParticles2D::is_emitting() const { - return emitting; } -int CPUParticles2D::get_amount() const { +int CPUParticles2D::get_amount() const { return particles.size(); } -float CPUParticles2D::get_lifetime() const { +double CPUParticles2D::get_lifetime() const { return lifetime; } -bool CPUParticles2D::get_one_shot() const { +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; } -float CPUParticles2D::get_explosiveness_ratio() const { +real_t CPUParticles2D::get_explosiveness_ratio() const { return explosiveness_ratio; } -float CPUParticles2D::get_randomness_ratio() const { +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; } bool CPUParticles2D::get_use_local_coordinates() const { - return local_coords; } -float CPUParticles2D::get_speed_scale() const { - +double CPUParticles2D::get_speed_scale() const { return speed_scale; } void CPUParticles2D::set_draw_order(DrawOrder p_order) { - draw_order = p_order; } CPUParticles2D::DrawOrder CPUParticles2D::get_draw_order() const { - return draw_order; } void CPUParticles2D::_update_mesh_texture() { - Size2 tex_size; if (texture.is_valid()) { tex_size = texture->get_size(); @@ -209,23 +197,25 @@ void CPUParticles2D::_update_mesh_texture() { } void CPUParticles2D::set_texture(const Ref<Texture2D> &p_texture) { - if (p_texture == texture) + if (p_texture == texture) { return; + } - if (texture.is_valid()) + if (texture.is_valid()) { texture->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CPUParticles2D::_texture_changed)); + } texture = p_texture; - if (texture.is_valid()) + if (texture.is_valid()) { texture->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &CPUParticles2D::_texture_changed)); + } update(); _update_mesh_texture(); } void CPUParticles2D::_texture_changed() { - if (texture.is_valid()) { update(); _update_mesh_texture(); @@ -233,21 +223,9 @@ void CPUParticles2D::_texture_changed() { } Ref<Texture2D> CPUParticles2D::get_texture() const { - return texture; } -void CPUParticles2D::set_normalmap(const Ref<Texture2D> &p_normalmap) { - - normalmap = p_normalmap; - update(); -} - -Ref<Texture2D> CPUParticles2D::get_normalmap() const { - - return normalmap; -} - void CPUParticles2D::set_fixed_fps(int p_count) { fixed_fps = p_count; } @@ -264,18 +242,15 @@ bool CPUParticles2D::get_fractional_delta() const { return fractional_delta; } -String CPUParticles2D::get_configuration_warning() const { - - String warnings; +TypedArray<String> CPUParticles2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); 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()) { - if (warnings != String()) - warnings += "\n"; - warnings += "- " + TTR("CPUParticles2D animation requires the usage of a CanvasItemMaterial with \"Particles Animation\" enabled."); + warnings.push_back(TTR("CPUParticles2D animation requires the usage of a CanvasItemMaterial with \"Particles Animation\" enabled.")); } } @@ -283,7 +258,6 @@ String CPUParticles2D::get_configuration_warning() const { } void CPUParticles2D::restart() { - time = 0; inactive_time = 0; frame_remainder = 0; @@ -303,62 +277,61 @@ void CPUParticles2D::restart() { } void CPUParticles2D::set_direction(Vector2 p_direction) { - direction = p_direction; } Vector2 CPUParticles2D::get_direction() const { - return direction; } -void CPUParticles2D::set_spread(float p_spread) { - +void CPUParticles2D::set_spread(real_t p_spread) { spread = p_spread; } -float CPUParticles2D::get_spread() const { - +real_t CPUParticles2D::get_spread() const { return spread; } -void CPUParticles2D::set_param(Parameter p_param, float 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); + } } -float 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, float 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); + } } -float 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, float p_min, float p_max) { - +static void _adjust_curve_range(const Ref<Curve> &p_curve, real_t p_min, real_t p_max) { Ref<Curve> curve = p_curve; - if (!curve.is_valid()) + if (!curve.is_valid()) { return; + } curve->ensure_default_setup(p_min, p_max); } void CPUParticles2D::set_param_curve(Parameter p_param, const Ref<Curve> &p_curve) { - ERR_FAIL_INDEX(p_param, PARAM_MAX); curve_parameters[p_param] = p_curve; @@ -389,7 +362,6 @@ void CPUParticles2D::set_param_curve(Parameter p_param, const Ref<Curve> &p_curv _adjust_curve_range(p_curve, -360, 360); } break; case PARAM_SCALE: { - } break; case PARAM_HUE_VARIATION: { _adjust_curve_range(p_curve, -1, 1); @@ -403,169 +375,180 @@ void CPUParticles2D::set_param_curve(Parameter p_param, const Ref<Curve> &p_curv } } } -Ref<Curve> CPUParticles2D::get_param_curve(Parameter p_param) const { +Ref<Curve> CPUParticles2D::get_param_curve(Parameter p_param) const { ERR_FAIL_INDEX_V(p_param, PARAM_MAX, Ref<Curve>()); return curve_parameters[p_param]; } void CPUParticles2D::set_color(const Color &p_color) { - color = p_color; } Color CPUParticles2D::get_color() const { - return color; } void CPUParticles2D::set_color_ramp(const Ref<Gradient> &p_ramp) { - color_ramp = p_ramp; } Ref<Gradient> CPUParticles2D::get_color_ramp() const { - return color_ramp; } -void CPUParticles2D::set_particle_flag(Flags p_flag, bool p_enable) { - ERR_FAIL_INDEX(p_flag, FLAG_MAX); - flags[p_flag] = p_enable; +void CPUParticles2D::set_particle_flag(ParticleFlags p_particle_flag, bool p_enable) { + ERR_FAIL_INDEX(p_particle_flag, PARTICLE_FLAG_MAX); + particle_flags[p_particle_flag] = p_enable; } -bool CPUParticles2D::get_particle_flag(Flags p_flag) const { - ERR_FAIL_INDEX_V(p_flag, FLAG_MAX, false); - return flags[p_flag]; +bool CPUParticles2D::get_particle_flag(ParticleFlags p_particle_flag) const { + ERR_FAIL_INDEX_V(p_particle_flag, PARTICLE_FLAG_MAX, false); + return particle_flags[p_particle_flag]; } void CPUParticles2D::set_emission_shape(EmissionShape p_shape) { ERR_FAIL_INDEX(p_shape, EMISSION_SHAPE_MAX); emission_shape = p_shape; - _change_notify(); + notify_property_list_changed(); } -void CPUParticles2D::set_emission_sphere_radius(float p_radius) { - +void CPUParticles2D::set_emission_sphere_radius(real_t p_radius) { emission_sphere_radius = p_radius; } void CPUParticles2D::set_emission_rect_extents(Vector2 p_extents) { - emission_rect_extents = p_extents; } void CPUParticles2D::set_emission_points(const Vector<Vector2> &p_points) { - emission_points = p_points; } void CPUParticles2D::set_emission_normals(const Vector<Vector2> &p_normals) { - emission_normals = p_normals; } void CPUParticles2D::set_emission_colors(const Vector<Color> &p_colors) { - emission_colors = p_colors; } -float CPUParticles2D::get_emission_sphere_radius() const { - +real_t CPUParticles2D::get_emission_sphere_radius() const { return emission_sphere_radius; } -Vector2 CPUParticles2D::get_emission_rect_extents() const { +Vector2 CPUParticles2D::get_emission_rect_extents() const { return emission_rect_extents; } -Vector<Vector2> CPUParticles2D::get_emission_points() const { +Vector<Vector2> CPUParticles2D::get_emission_points() const { return emission_points; } -Vector<Vector2> CPUParticles2D::get_emission_normals() const { +Vector<Vector2> CPUParticles2D::get_emission_normals() const { return emission_normals; } Vector<Color> CPUParticles2D::get_emission_colors() const { - return emission_colors; } CPUParticles2D::EmissionShape CPUParticles2D::get_emission_shape() const { return emission_shape; } -void CPUParticles2D::set_gravity(const Vector2 &p_gravity) { +void CPUParticles2D::set_gravity(const Vector2 &p_gravity) { gravity = p_gravity; } Vector2 CPUParticles2D::get_gravity() const { - return gravity; } -void CPUParticles2D::_validate_property(PropertyInfo &property) const { +void CPUParticles2D::set_scale_curve_x(Ref<Curve> p_scale_curve) { + scale_curve_x = p_scale_curve; +} - if (property.name == "color" && color_ramp.is_valid()) { - property.usage = 0; - } +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 = 0; + property.usage = PROPERTY_USAGE_NONE; } if (property.name == "emission_rect_extents" && emission_shape != EMISSION_SHAPE_RECTANGLE) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } if ((property.name == "emission_point_texture" || property.name == "emission_color_texture") && (emission_shape < EMISSION_SHAPE_POINTS)) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } if (property.name == "emission_normals" && emission_shape != EMISSION_SHAPE_DIRECTED_POINTS) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } if (property.name == "emission_points" && emission_shape != EMISSION_SHAPE_POINTS && emission_shape != EMISSION_SHAPE_DIRECTED_POINTS) { - property.usage = 0; + property.usage = PROPERTY_USAGE_NONE; } if (property.name == "emission_colors" && emission_shape != EMISSION_SHAPE_POINTS && emission_shape != EMISSION_SHAPE_DIRECTED_POINTS) { - property.usage = 0; + 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) { - x = ((x >> uint32_t(16)) ^ x) * uint32_t(0x45d9f3b); x = ((x >> uint32_t(16)) ^ x) * uint32_t(0x45d9f3b); x = (x >> uint32_t(16)) ^ x; return x; } -static float rand_from_seed(uint32_t &seed) { +static real_t rand_from_seed(uint32_t &seed) { int k; int s = int(seed); - if (s == 0) + if (s == 0) { s = 305420679; + } k = s / 127773; s = 16807 * (s - k * 127773) - 2836 * k; - if (s < 0) + if (s < 0) { s += 2147483647; + } seed = uint32_t(s); - return float(seed % uint32_t(65536)) / 65535.0; + return (seed % uint32_t(65536)) / 65535.0; } void CPUParticles2D::_update_internal() { - if (particles.size() == 0 || !is_visible_in_tree()) { _set_redraw(false); return; } - float delta = get_process_delta_time(); + double delta = get_process_delta_time(); if (emitting) { inactive_time = 0; } else { @@ -585,14 +568,14 @@ void CPUParticles2D::_update_internal() { _set_redraw(true); if (time == 0 && pre_process_time > 0.0) { - - float frame_time; - if (fixed_fps > 0) + double frame_time; + if (fixed_fps > 0) { frame_time = 1.0 / fixed_fps; - else + } else { frame_time = 1.0 / 30.0; + } - float todo = pre_process_time; + double todo = pre_process_time; while (todo >= 0) { _particles_process(frame_time); @@ -601,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); @@ -626,8 +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(); @@ -635,14 +617,14 @@ 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); cycle++; if (one_shot && cycle > 0) { set_emitting(false); - _change_notify(); + notify_property_list_changed(); } } @@ -654,21 +636,21 @@ 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]; - if (!emitting && !p.active) + if (!emitting && !p.active) { continue; + } - float local_delta = p_delta; + double local_delta = p_delta; // The phase is a ratio between 0 (birth) and 1 (end of life) for each particle. // While we use time in tests later on, for randomness we use the phase as done in the // original shader code, and we later multiply by lifetime to get the time. - float restart_phase = float(i) / float(pcount); + double restart_phase = double(i) / double(pcount); if (randomness_ratio > 0.0) { uint32_t seed = cycle; @@ -677,12 +659,12 @@ void CPUParticles2D::_particles_process(float p_delta) { } seed *= uint32_t(pcount); seed += uint32_t(i); - float random = float(idhash(seed) % uint32_t(65536)) / 65536.0; - restart_phase += randomness_ratio * random * 1.0 / float(pcount); + double random = double(idhash(seed) % uint32_t(65536)) / 65536.0; + restart_phase += randomness_ratio * random * 1.0 / double(pcount); } restart_phase *= (1.0 - explosiveness_ratio); - float restart_time = restart_phase * lifetime; + double restart_time = restart_phase * lifetime; bool restart = false; if (time > prev_time) { @@ -714,27 +696,28 @@ void CPUParticles2D::_particles_process(float p_delta) { restart = true; } - if (restart) { + float tv = 0.0; + if (restart) { if (!emitting) { p.active = false; continue; } p.active = true; - /*float tex_linear_velocity = 0; + /*real_t tex_linear_velocity = 0; if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { tex_linear_velocity = curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY]->interpolate(0); }*/ - float tex_angle = 0.0; + real_t tex_angle = 0.0; if (curve_parameters[PARAM_ANGLE].is_valid()) { - tex_angle = curve_parameters[PARAM_ANGLE]->interpolate(0); + tex_angle = curve_parameters[PARAM_ANGLE]->interpolate(tv); } - float tex_anim_offset = 0.0; + real_t tex_anim_offset = 0.0; if (curve_parameters[PARAM_ANGLE].is_valid()) { - tex_anim_offset = curve_parameters[PARAM_ANGLE]->interpolate(0); + tex_anim_offset = curve_parameters[PARAM_ANGLE]->interpolate(tv); } p.seed = Math::rand(); @@ -744,16 +727,16 @@ void CPUParticles2D::_particles_process(float p_delta) { p.hue_rot_rand = Math::randf(); p.anim_offset_rand = Math::randf(); - float angle1_rad = Math::atan2(direction.y, direction.x) + (Math::randf() * 2.0 - 1.0) * Math_PI * spread / 180.0; + 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(1.0f, float(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()); - float base_angle = (parameters[PARAM_ANGLE] + tex_angle) * Math::lerp(1.0f, p.angle_rand, randomness[PARAM_ANGLE]); + real_t base_angle = tex_angle * Math::lerp(parameters_min[PARAM_ANGLE], parameters_max[PARAM_ANGLE], p.angle_rand); p.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(1.0f, 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; @@ -765,8 +748,8 @@ void CPUParticles2D::_particles_process(float p_delta) { //do none } break; case EMISSION_SHAPE_SPHERE: { - float s = Math::randf(), t = 2.0 * Math_PI * Math::randf(); - float radius = emission_sphere_radius * Math::sqrt(1.0 - s * s); + real_t s = Math::randf(), t = Math_TAU * Math::randf(); + real_t radius = emission_sphere_radius * Math::sqrt(1.0 - s * s); p.transform[2] = Vector2(Math::cos(t), Math::sin(t)) * radius; } break; case EMISSION_SHAPE_RECTANGLE: { @@ -774,17 +757,21 @@ void CPUParticles2D::_particles_process(float p_delta) { } break; case EMISSION_SHAPE_POINTS: case EMISSION_SHAPE_DIRECTED_POINTS: { - int pc = emission_points.size(); - if (pc == 0) + if (pc == 0) { break; + } int random_idx = Math::rand() % pc; p.transform[2] = emission_points.get(random_idx); if (emission_shape == EMISSION_SHAPE_DIRECTED_POINTS && emission_normals.size() == pc) { - p.velocity = emission_normals.get(random_idx); + Vector2 normal = emission_normals.get(random_idx); + Transform2D m2; + m2.set_axis(0, normal); + m2.set_axis(1, normal.orthogonal()); + p.velocity = m2.basis_xform(p.velocity); } if (emission_colors.size() == pc) { @@ -805,80 +792,81 @@ void CPUParticles2D::_particles_process(float p_delta) { continue; } else if (p.time > p.lifetime) { p.active = false; + tv = 1.0; } else { - uint32_t alt_seed = p.seed; p.time += local_delta; p.custom[1] = p.time / lifetime; + tv = p.time / p.lifetime; - float 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(p.custom[1]); + tex_linear_velocity = curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY]->interpolate(tv); } - float 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(p.custom[1]); + tex_orbit_velocity = curve_parameters[PARAM_ORBIT_VELOCITY]->interpolate(tv); } - float 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(p.custom[1]); + tex_angular_velocity = curve_parameters[PARAM_ANGULAR_VELOCITY]->interpolate(tv); } - float 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(p.custom[1]); + tex_linear_accel = curve_parameters[PARAM_LINEAR_ACCEL]->interpolate(tv); } - float 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(p.custom[1]); + tex_tangential_accel = curve_parameters[PARAM_TANGENTIAL_ACCEL]->interpolate(tv); } - float 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(p.custom[1]); + tex_radial_accel = curve_parameters[PARAM_RADIAL_ACCEL]->interpolate(tv); } - float tex_damping = 0.0; + real_t tex_damping = 1.0; if (curve_parameters[PARAM_DAMPING].is_valid()) { - tex_damping = curve_parameters[PARAM_DAMPING]->interpolate(p.custom[1]); + tex_damping = curve_parameters[PARAM_DAMPING]->interpolate(tv); } - float tex_angle = 0.0; + real_t tex_angle = 1.0; if (curve_parameters[PARAM_ANGLE].is_valid()) { - tex_angle = curve_parameters[PARAM_ANGLE]->interpolate(p.custom[1]); + tex_angle = curve_parameters[PARAM_ANGLE]->interpolate(tv); } - float 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(p.custom[1]); + tex_anim_speed = curve_parameters[PARAM_ANIM_SPEED]->interpolate(tv); } - float 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(p.custom[1]); + tex_anim_offset = curve_parameters[PARAM_ANIM_OFFSET]->interpolate(tv); } Vector2 force = gravity; 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(1.0f, 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(1.0f, 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(1.0f, 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 - float orbit_amount = (parameters[PARAM_ORBIT_VELOCITY] + tex_orbit_velocity) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_ORBIT_VELOCITY]); + real_t orbit_amount = tex_orbit_velocity * Math::lerp(parameters_min[PARAM_ORBIT_VELOCITY], parameters_max[PARAM_ORBIT_VELOCITY], rand_from_seed(alt_seed)); if (orbit_amount != 0.0) { - float ang = orbit_amount * local_delta * Math_PI * 2.0; + real_t ang = orbit_amount * local_delta * Math_TAU; // Not sure why the ParticlesMaterial code uses a clockwise rotation matrix, // but we use -ang here to reproduce its behavior. Transform2D rot = Transform2D(-ang, Vector2()); @@ -889,10 +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) { - - float v = p.velocity.length(); - float damp = (parameters[PARAM_DAMPING] + tex_damping) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_DAMPING]); + if (parameters_max[PARAM_DAMPING] + tex_damping > 0.0) { + real_t v = p.velocity.length(); + real_t damp = tex_damping * Math::lerp(parameters_min[PARAM_DAMPING], parameters_max[PARAM_DAMPING], rand_from_seed(alt_seed)); v -= damp * local_delta; if (v < 0.0) { p.velocity = Vector2(); @@ -900,28 +887,42 @@ void CPUParticles2D::_particles_process(float p_delta) { p.velocity = p.velocity.normalized() * v; } } - float base_angle = (parameters[PARAM_ANGLE] + tex_angle) * Math::lerp(1.0f, p.angle_rand, randomness[PARAM_ANGLE]); - base_angle += p.custom[1] * lifetime * (parameters[PARAM_ANGULAR_VELOCITY] + tex_angular_velocity) * Math::lerp(1.0f, rand_from_seed(alt_seed) * 2.0f - 1.0f, randomness[PARAM_ANGULAR_VELOCITY]); + real_t base_angle = (tex_angle)*Math::lerp(parameters_min[PARAM_ANGLE], parameters_max[PARAM_ANGLE], p.angle_rand); + base_angle += p.custom[1] * lifetime * tex_angular_velocity * Math::lerp(parameters_min[PARAM_ANGULAR_VELOCITY], parameters_max[PARAM_ANGULAR_VELOCITY], rand_from_seed(alt_seed)); p.rotation = Math::deg2rad(base_angle); //angle - float animation_phase = (parameters[PARAM_ANIM_OFFSET] + tex_anim_offset) * Math::lerp(1.0f, p.anim_offset_rand, randomness[PARAM_ANIM_OFFSET]) + p.custom[1] * (parameters[PARAM_ANIM_SPEED] + tex_anim_speed) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_ANIM_SPEED]); - 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 - float tex_scale = 1.0; - if (curve_parameters[PARAM_SCALE].is_valid()) { - tex_scale = curve_parameters[PARAM_SCALE]->interpolate(p.custom[1]); + 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; + } } - float tex_hue_variation = 0.0; + real_t tex_hue_variation = 0.0; if (curve_parameters[PARAM_HUE_VARIATION].is_valid()) { - tex_hue_variation = curve_parameters[PARAM_HUE_VARIATION]->interpolate(p.custom[1]); + tex_hue_variation = curve_parameters[PARAM_HUE_VARIATION]->interpolate(tv); } - float hue_rot_angle = (parameters[PARAM_HUE_VARIATION] + tex_hue_variation) * Math_PI * 2.0 * Math::lerp(1.0f, p.hue_rot_rand * 2.0f - 1.0f, randomness[PARAM_HUE_VARIATION]); - float hue_rot_c = Math::cos(hue_rot_angle); - float hue_rot_s = Math::sin(hue_rot_angle); + real_t hue_rot_angle = (tex_hue_variation)*Math_TAU * Math::lerp(parameters_min[PARAM_HUE_VARIATION], parameters_max[PARAM_HUE_VARIATION], p.hue_rot_rand); + real_t hue_rot_c = Math::cos(hue_rot_angle); + real_t hue_rot_s = Math::sin(hue_rot_angle); Basis hue_rot_mat; { @@ -935,7 +936,7 @@ void CPUParticles2D::_particles_process(float p_delta) { } if (color_ramp.is_valid()) { - p.color = color_ramp->get_color_at_offset(p.custom[1]) * color; + p.color = color_ramp->get_color_at_offset(tv) * color; } else { p.color = color; } @@ -947,11 +948,10 @@ void CPUParticles2D::_particles_process(float p_delta) { p.color *= p.base_color; - if (flags[FLAG_ALIGN_Y_TO_VELOCITY]) { + if (particle_flags[PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY]) { if (p.velocity.length() > 0.0) { - p.transform.elements[1] = p.velocity.normalized(); - p.transform.elements[0] = p.transform.elements[1].tangent(); + p.transform.elements[0] = p.transform.elements[1].orthogonal(); } } else { @@ -960,11 +960,15 @@ void CPUParticles2D::_particles_process(float p_delta) { } //scale by scale - float base_scale = tex_scale * Math::lerp(parameters[PARAM_SCALE], 1.0f, p.scale_rand * randomness[PARAM_SCALE]); - if (base_scale < 0.000001) base_scale = 0.000001; - - p.transform.elements[0] *= base_scale; - p.transform.elements[1] *= base_scale; + 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; + } + 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; } @@ -997,7 +1001,6 @@ void CPUParticles2D::_update_particle_data_buffer() { } for (int i = 0; i < pc; i++) { - int idx = order ? order[i] : i; Transform2D t = r[idx].transform; @@ -1007,7 +1010,6 @@ void CPUParticles2D::_update_particle_data_buffer() { } if (r[idx].active) { - ptr[0] = t.elements[0][0]; ptr[1] = t.elements[1][0]; ptr[2] = 0; @@ -1018,7 +1020,7 @@ void CPUParticles2D::_update_particle_data_buffer() { ptr[7] = t.elements[2][1]; } else { - zeromem(ptr, sizeof(float) * 8); + memset(ptr, 0, sizeof(float) * 8); } Color c = r[idx].color; @@ -1038,8 +1040,9 @@ void CPUParticles2D::_update_particle_data_buffer() { } void CPUParticles2D::_set_redraw(bool p_redraw) { - if (redraw == p_redraw) + if (redraw == p_redraw) { return; + } redraw = p_redraw; { @@ -1064,86 +1067,74 @@ void CPUParticles2D::_set_redraw(bool p_redraw) { } void CPUParticles2D::_update_render_thread() { - MutexLock lock(update_mutex); RS::get_singleton()->multimesh_set_buffer(multimesh, particle_data); } void CPUParticles2D::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + set_process_internal(emitting); + } break; + case NOTIFICATION_EXIT_TREE: { + _set_redraw(false); + } break; + case NOTIFICATION_DRAW: { + // first update before rendering to avoid one frame delay after emitting starts + if (emitting && (time == 0)) { + _update_internal(); + } - if (p_what == NOTIFICATION_ENTER_TREE) { - set_process_internal(emitting); - } + if (!redraw) { + return; // don't add to render list + } - if (p_what == NOTIFICATION_EXIT_TREE) { - _set_redraw(false); - } + RID texrid; + if (texture.is_valid()) { + texrid = texture->get_rid(); + } - if (p_what == NOTIFICATION_DRAW) { - // first update before rendering to avoid one frame delay after emitting starts - if (emitting && (time == 0)) + RS::get_singleton()->canvas_item_add_multimesh(get_canvas_item(), multimesh, texrid); + } break; + case NOTIFICATION_INTERNAL_PROCESS: { _update_internal(); + } break; + case NOTIFICATION_TRANSFORM_CHANGED: { + inv_emission_transform = get_global_transform().affine_inverse(); - if (!redraw) - return; // don't add to render list - - RID texrid; - if (texture.is_valid()) { - texrid = texture->get_rid(); - } - - RID normrid; - if (normalmap.is_valid()) { - normrid = normalmap->get_rid(); - } - - RS::get_singleton()->canvas_item_add_multimesh(get_canvas_item(), multimesh, texrid, normrid); - } - - if (p_what == NOTIFICATION_INTERNAL_PROCESS) { - _update_internal(); - } - - if (p_what == NOTIFICATION_TRANSFORM_CHANGED) { - - inv_emission_transform = get_global_transform().affine_inverse(); - - if (!local_coords) { - - int pc = particles.size(); - - float *w = particle_data.ptrw(); - const Particle *r = particles.ptr(); - float *ptr = w; - - for (int i = 0; i < pc; i++) { - - Transform2D t = inv_emission_transform * r[i].transform; - - if (r[i].active) { - - ptr[0] = t.elements[0][0]; - ptr[1] = t.elements[1][0]; - ptr[2] = 0; - ptr[3] = t.elements[2][0]; - ptr[4] = t.elements[0][1]; - ptr[5] = t.elements[1][1]; - ptr[6] = 0; - ptr[7] = t.elements[2][1]; + if (!local_coords) { + int pc = particles.size(); + + float *w = particle_data.ptrw(); + const Particle *r = particles.ptr(); + float *ptr = w; + + for (int i = 0; i < pc; i++) { + Transform2D t = inv_emission_transform * r[i].transform; + + if (r[i].active) { + ptr[0] = t.elements[0][0]; + ptr[1] = t.elements[1][0]; + ptr[2] = 0; + ptr[3] = t.elements[2][0]; + ptr[4] = t.elements[0][1]; + ptr[5] = t.elements[1][1]; + ptr[6] = 0; + ptr[7] = t.elements[2][1]; + + } else { + memset(ptr, 0, sizeof(float) * 8); + } - } else { - zeromem(ptr, sizeof(float) * 8); + ptr += 16; } - - ptr += 16; } - } + } break; } } void CPUParticles2D::convert_from_particles(Node *p_particles) { - GPUParticles2D *particles = Object::cast_to<GPUParticles2D>(p_particles); ERR_FAIL_COND_MSG(!particles, "Only GPUParticles2D nodes can be converted to CPUParticles2D."); @@ -1167,8 +1158,9 @@ void CPUParticles2D::convert_from_particles(Node *p_particles) { } Ref<ParticlesMaterial> material = particles->get_process_material(); - if (material.is_null()) + if (material.is_null()) { return; + } Vector3 dir = material->get_direction(); set_direction(Vector2(dir.x, dir.y)); @@ -1181,24 +1173,31 @@ void CPUParticles2D::convert_from_particles(Node *p_particles) { set_color_ramp(gt->get_gradient()); } - set_particle_flag(FLAG_ALIGN_Y_TO_VELOCITY, material->get_flag(ParticlesMaterial::FLAG_ALIGN_Y_TO_VELOCITY)); + set_particle_flag(PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY, material->get_particle_flag(ParticlesMaterial::PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY)); set_emission_shape(EmissionShape(material->get_emission_shape())); set_emission_sphere_radius(material->get_emission_sphere_radius()); 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()); \ + 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); @@ -1217,7 +1216,6 @@ void CPUParticles2D::convert_from_particles(Node *p_particles) { } void CPUParticles2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_emitting", "emitting"), &CPUParticles2D::set_emitting); ClassDB::bind_method(D_METHOD("set_amount", "amount"), &CPUParticles2D::set_amount); ClassDB::bind_method(D_METHOD("set_lifetime", "secs"), &CPUParticles2D::set_lifetime); @@ -1251,13 +1249,10 @@ void CPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_texture", "texture"), &CPUParticles2D::set_texture); ClassDB::bind_method(D_METHOD("get_texture"), &CPUParticles2D::get_texture); - ClassDB::bind_method(D_METHOD("set_normalmap", "normalmap"), &CPUParticles2D::set_normalmap); - ClassDB::bind_method(D_METHOD("get_normalmap"), &CPUParticles2D::get_normalmap); - ClassDB::bind_method(D_METHOD("restart"), &CPUParticles2D::restart); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_EXP_RANGE, "1,1000000,1"), "set_amount", "get_amount"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount"); ADD_GROUP("Time", ""); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater"), "set_lifetime", "get_lifetime"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot"); @@ -1273,7 +1268,6 @@ void CPUParticles2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "local_coords"), "set_use_local_coordinates", "get_use_local_coordinates"); ADD_PROPERTY(PropertyInfo(Variant::INT, "draw_order", PROPERTY_HINT_ENUM, "Index,Lifetime"), "set_draw_order", "get_draw_order"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "normalmap", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_normalmap", "get_normalmap"); BIND_ENUM_CONSTANT(DRAW_ORDER_INDEX); BIND_ENUM_CONSTANT(DRAW_ORDER_LIFETIME); @@ -1286,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); @@ -1301,8 +1295,8 @@ void CPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_color_ramp", "ramp"), &CPUParticles2D::set_color_ramp); ClassDB::bind_method(D_METHOD("get_color_ramp"), &CPUParticles2D::get_color_ramp); - ClassDB::bind_method(D_METHOD("set_particle_flag", "flag", "enable"), &CPUParticles2D::set_particle_flag); - ClassDB::bind_method(D_METHOD("get_particle_flag", "flag"), &CPUParticles2D::get_particle_flag); + ClassDB::bind_method(D_METHOD("set_particle_flag", "particle_flag", "enable"), &CPUParticles2D::set_particle_flag); + ClassDB::bind_method(D_METHOD("get_particle_flag", "particle_flag"), &CPUParticles2D::get_particle_flag); ClassDB::bind_method(D_METHOD("set_emission_shape", "shape"), &CPUParticles2D::set_emission_shape); ClassDB::bind_method(D_METHOD("get_emission_shape"), &CPUParticles2D::get_emission_shape); @@ -1325,71 +1319,84 @@ 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_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_shape", PROPERTY_HINT_ENUM, "Point,Sphere,Box,Points,Directed Points"), "set_emission_shape", "get_emission_shape"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_shape", PROPERTY_HINT_ENUM, "Point,Sphere,Box,Points,Directed Points", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_emission_shape", "get_emission_shape"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_sphere_radius", PROPERTY_HINT_RANGE, "0.01,128,0.01"), "set_emission_sphere_radius", "get_emission_sphere_radius"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "emission_rect_extents"), "set_emission_rect_extents", "get_emission_rect_extents"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "emission_points"), "set_emission_points", "get_emission_points"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "emission_normals"), "set_emission_normals", "get_emission_normals"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "emission_colors"), "set_emission_colors", "get_emission_colors"); - ADD_GROUP("Flags", "flag_"); - ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flag_align_y"), "set_particle_flag", "get_particle_flag", FLAG_ALIGN_Y_TO_VELOCITY); + ADD_GROUP("Particle Flags", "particle_flag_"); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_align_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY); ADD_GROUP("Direction", ""); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "direction"), "set_direction", "get_direction"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "spread", PROPERTY_HINT_RANGE, "0,180,0.01"), "set_spread", "get_spread"); ADD_GROUP("Gravity", ""); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "gravity"), "set_gravity", "get_gravity"); ADD_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"), "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); @@ -1406,10 +1413,10 @@ void CPUParticles2D::_bind_methods() { BIND_ENUM_CONSTANT(PARAM_ANIM_OFFSET); BIND_ENUM_CONSTANT(PARAM_MAX); - BIND_ENUM_CONSTANT(FLAG_ALIGN_Y_TO_VELOCITY); - BIND_ENUM_CONSTANT(FLAG_ROTATE_Y); // Unused, but exposed for consistency with 3D. - BIND_ENUM_CONSTANT(FLAG_DISABLE_Z); // Unused, but exposed for consistency with 3D. - BIND_ENUM_CONSTANT(FLAG_MAX); + BIND_ENUM_CONSTANT(PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY); + BIND_ENUM_CONSTANT(PARTICLE_FLAG_ROTATE_Y); // Unused, but exposed for consistency with 3D. + BIND_ENUM_CONSTANT(PARTICLE_FLAG_DISABLE_Z); // Unused, but exposed for consistency with 3D. + BIND_ENUM_CONSTANT(PARTICLE_FLAG_MAX); BIND_ENUM_CONSTANT(EMISSION_SHAPE_POINT); BIND_ENUM_CONSTANT(EMISSION_SHAPE_SPHERE); @@ -1420,59 +1427,42 @@ void CPUParticles2D::_bind_methods() { } CPUParticles2D::CPUParticles2D() { - - time = 0; - inactive_time = 0; - frame_remainder = 0; - cycle = 0; - redraw = false; - emitting = false; - mesh = RenderingServer::get_singleton()->mesh_create(); multimesh = RenderingServer::get_singleton()->multimesh_create(); RenderingServer::get_singleton()->multimesh_set_mesh(multimesh, mesh); set_emitting(true); - set_one_shot(false); set_amount(8); - set_lifetime(1); - set_fixed_fps(0); - set_fractional_delta(true); - set_pre_process_time(0); - set_explosiveness_ratio(0); - set_randomness_ratio(0); - set_lifetime_randomness(0); set_use_local_coordinates(true); - set_draw_order(DRAW_ORDER_INDEX); - set_speed_scale(1); - - set_direction(Vector2(1, 0)); - set_spread(45); - set_param(PARAM_INITIAL_LINEAR_VELOCITY, 0); - set_param(PARAM_ANGULAR_VELOCITY, 0); - set_param(PARAM_ORBIT_VELOCITY, 0); - set_param(PARAM_LINEAR_ACCEL, 0); - set_param(PARAM_RADIAL_ACCEL, 0); - set_param(PARAM_TANGENTIAL_ACCEL, 0); - set_param(PARAM_DAMPING, 0); - set_param(PARAM_ANGLE, 0); - set_param(PARAM_SCALE, 1); - set_param(PARAM_HUE_VARIATION, 0); - set_param(PARAM_ANIM_SPEED, 0); - set_param(PARAM_ANIM_OFFSET, 0); - set_emission_shape(EMISSION_SHAPE_POINT); - set_emission_sphere_radius(1); - set_emission_rect_extents(Vector2(1, 1)); - - set_gravity(Vector2(0, 98)); - - for (int i = 0; i < PARAM_MAX; i++) { - set_param_randomness(Parameter(i), 0); - } - - for (int i = 0; i < FLAG_MAX; i++) { - flags[i] = false; + 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; } set_color(Color(1, 1, 1, 1)); diff --git a/scene/2d/cpu_particles_2d.h b/scene/2d/cpu_particles_2d.h index 18d0caceed..4990d443e3 100644 --- a/scene/2d/cpu_particles_2d.h +++ b/scene/2d/cpu_particles_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,9 +31,7 @@ #ifndef CPU_PARTICLES_2D_H #define CPU_PARTICLES_2D_H -#include "core/rid.h" #include "scene/2d/node_2d.h" -#include "scene/resources/texture.h" class CPUParticles2D : public Node2D { private: @@ -46,7 +44,6 @@ public: }; enum Parameter { - PARAM_INITIAL_LINEAR_VELOCITY, PARAM_ANGULAR_VELOCITY, PARAM_ORBIT_VELOCITY, @@ -62,11 +59,11 @@ public: PARAM_MAX }; - enum Flags { - FLAG_ALIGN_Y_TO_VELOCITY, - FLAG_ROTATE_Y, // Unused, but exposed for consistency with 3D. - FLAG_DISABLE_Z, // Unused, but exposed for consistency with 3D. - FLAG_MAX + enum ParticleFlags { + PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY, + PARTICLE_FLAG_ROTATE_Y, // Unused, but exposed for consistency with 3D. + PARTICLE_FLAG_DISABLE_Z, // Unused, but exposed for consistency with 3D. + PARTICLE_FLAG_MAX }; enum EmissionShape { @@ -79,31 +76,31 @@ public: }; private: - bool emitting; + bool emitting = false; struct Particle { Transform2D transform; Color color; - float custom[4]; - float rotation; + real_t custom[4] = {}; + real_t rotation = 0.0; Vector2 velocity; - bool active; - float angle_rand; - float scale_rand; - float hue_rot_rand; - float anim_offset_rand; - float time; - float lifetime; + bool active = false; + real_t angle_rand = 0.0; + real_t scale_rand = 0.0; + real_t hue_rot_rand = 0.0; + real_t anim_offset_rand = 0.0; + double time = 0.0; + double lifetime = 0.0; Color base_color; - uint32_t seed; + uint32_t seed = 0; }; - float time; - float inactive_time; - float frame_remainder; - int cycle; - bool redraw; + double time = 0.0; + double inactive_time = 0.0; + double frame_remainder = 0.0; + int cycle = 0; + bool redraw = false; RID mesh; RID multimesh; @@ -113,7 +110,7 @@ private: Vector<int> particle_order; struct SortLifetime { - const Particle *particles; + const Particle *particles = nullptr; bool operator()(int p_a, int p_b) const { return particles[p_a].time > particles[p_b].time; @@ -121,61 +118,63 @@ private: }; struct SortAxis { - const Particle *particles; + const Particle *particles = nullptr; Vector2 axis; bool operator()(int p_a, int p_b) const { - return axis.dot(particles[p_a].transform[2]) < axis.dot(particles[p_b].transform[2]); } }; // - bool one_shot; + bool one_shot = false; - float lifetime; - float pre_process_time; - float explosiveness_ratio; - float randomness_ratio; - float lifetime_randomness; - float speed_scale; + double lifetime = 1.0; + double pre_process_time = 0.0; + real_t explosiveness_ratio = 0.0; + real_t randomness_ratio = 0.0; + double lifetime_randomness = 0.0; + double speed_scale = 1.0; bool local_coords; - int fixed_fps; - bool fractional_delta; + int fixed_fps = 0; + bool fractional_delta = true; Transform2D inv_emission_transform; - DrawOrder draw_order; + DrawOrder draw_order = DRAW_ORDER_INDEX; Ref<Texture2D> texture; - Ref<Texture2D> normalmap; //////// - Vector2 direction; - float spread; + Vector2 direction = Vector2(1, 0); + real_t spread = 45.0; - float parameters[PARAM_MAX]; - float randomness[PARAM_MAX]; + real_t parameters_min[PARAM_MAX]; + real_t parameters_max[PARAM_MAX]; Ref<Curve> curve_parameters[PARAM_MAX]; Color color; Ref<Gradient> color_ramp; - bool flags[FLAG_MAX]; + bool particle_flags[PARTICLE_FLAG_MAX]; - EmissionShape emission_shape; - float emission_sphere_radius; - Vector2 emission_rect_extents; + EmissionShape emission_shape = EMISSION_SHAPE_POINT; + real_t emission_sphere_radius = 1.0; + Vector2 emission_rect_extents = Vector2(1, 1); Vector<Vector2> emission_points; Vector<Vector2> emission_normals; Vector<Color> emission_colors; - int emission_point_count; + int emission_point_count = 0; - Vector2 gravity; + 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; @@ -191,32 +190,32 @@ private: protected: static void _bind_methods(); void _notification(int p_what); - virtual void _validate_property(PropertyInfo &property) const; + virtual void _validate_property(PropertyInfo &property) const override; public: void set_emitting(bool p_emitting); void set_amount(int p_amount); - void set_lifetime(float p_lifetime); + void set_lifetime(double p_lifetime); void set_one_shot(bool p_one_shot); - void set_pre_process_time(float p_time); - void set_explosiveness_ratio(float p_ratio); - void set_randomness_ratio(float p_ratio); - void set_lifetime_randomness(float p_random); + void set_pre_process_time(double p_time); + void set_explosiveness_ratio(real_t p_ratio); + void set_randomness_ratio(real_t p_ratio); + void set_lifetime_randomness(double p_random); void set_visibility_aabb(const Rect2 &p_aabb); void set_use_local_coordinates(bool p_enable); - void set_speed_scale(float p_scale); + void set_speed_scale(double p_scale); bool is_emitting() const; int get_amount() const; - float get_lifetime() const; + double get_lifetime() const; bool get_one_shot() const; - float get_pre_process_time() const; - float get_explosiveness_ratio() const; - float get_randomness_ratio() const; - float get_lifetime_randomness() const; + double get_pre_process_time() const; + real_t get_explosiveness_ratio() const; + real_t get_randomness_ratio() const; + double get_lifetime_randomness() const; Rect2 get_visibility_aabb() const; bool get_use_local_coordinates() const; - float get_speed_scale() const; + double get_speed_scale() const; void set_fixed_fps(int p_count); int get_fixed_fps() const; @@ -233,22 +232,19 @@ public: void set_texture(const Ref<Texture2D> &p_texture); Ref<Texture2D> get_texture() const; - void set_normalmap(const Ref<Texture2D> &p_normalmap); - Ref<Texture2D> get_normalmap() const; - /////////////////// void set_direction(Vector2 p_direction); Vector2 get_direction() const; - void set_spread(float p_spread); - float get_spread() const; + void set_spread(real_t p_spread); + real_t get_spread() const; - void set_param(Parameter p_param, float p_value); - float get_param(Parameter p_param) const; + void set_param_min(Parameter p_param, real_t p_value); + real_t get_param_min(Parameter p_param) const; - void set_param_randomness(Parameter p_param, float p_value); - float get_param_randomness(Parameter p_param) const; + void set_param_max(Parameter p_param, real_t p_value); + real_t get_param_max(Parameter p_param) const; void set_param_curve(Parameter p_param, const Ref<Curve> &p_curve); Ref<Curve> get_param_curve(Parameter p_param) const; @@ -259,29 +255,35 @@ public: void set_color_ramp(const Ref<Gradient> &p_ramp); Ref<Gradient> get_color_ramp() const; - void set_particle_flag(Flags p_flag, bool p_enable); - bool get_particle_flag(Flags p_flag) const; + void set_particle_flag(ParticleFlags p_particle_flag, bool p_enable); + bool get_particle_flag(ParticleFlags p_particle_flag) const; void set_emission_shape(EmissionShape p_shape); - void set_emission_sphere_radius(float p_radius); + void set_emission_sphere_radius(real_t p_radius); void set_emission_rect_extents(Vector2 p_extents); void set_emission_points(const Vector<Vector2> &p_points); 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; - float get_emission_sphere_radius() const; + real_t get_emission_sphere_radius() const; Vector2 get_emission_rect_extents() const; Vector<Vector2> get_emission_points() const; 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; - virtual String get_configuration_warning() const; + TypedArray<String> get_configuration_warnings() const override; void restart(); @@ -293,7 +295,7 @@ public: VARIANT_ENUM_CAST(CPUParticles2D::DrawOrder) VARIANT_ENUM_CAST(CPUParticles2D::Parameter) -VARIANT_ENUM_CAST(CPUParticles2D::Flags) +VARIANT_ENUM_CAST(CPUParticles2D::ParticleFlags) VARIANT_ENUM_CAST(CPUParticles2D::EmissionShape) #endif // CPU_PARTICLES_2D_H diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp index de3f8fa09e..5bce705dd5 100644 --- a/scene/2d/gpu_particles_2d.cpp +++ b/scene/2d/gpu_particles_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,16 +30,13 @@ #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/engine.h" +#include "core/config/engine.h" #endif void GPUParticles2D::set_emitting(bool p_emitting) { - RS::get_singleton()->particles_set_emitting(particles, p_emitting); if (p_emitting && one_shot) { @@ -50,50 +47,49 @@ void GPUParticles2D::set_emitting(bool p_emitting) { } void GPUParticles2D::set_amount(int p_amount) { - ERR_FAIL_COND_MSG(p_amount < 1, "Amount of particles cannot be smaller than 1."); amount = 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); } void GPUParticles2D::set_one_shot(bool p_enable) { - one_shot = p_enable; RS::get_singleton()->particles_set_one_shot(particles, one_shot); if (is_emitting()) { - set_process_internal(true); - if (!one_shot) + if (!one_shot) { RenderingServer::get_singleton()->particles_restart(particles); + } } - if (!one_shot) + if (!one_shot) { set_process_internal(false); + } } -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); } -void GPUParticles2D::set_visibility_rect(const Rect2 &p_visibility_rect) { +void GPUParticles2D::set_visibility_rect(const Rect2 &p_visibility_rect) { visibility_rect = p_visibility_rect; AABB aabb; aabb.position.x = p_visibility_rect.position.x; @@ -103,11 +99,10 @@ void GPUParticles2D::set_visibility_rect(const Rect2 &p_visibility_rect) { RS::get_singleton()->particles_set_custom_aabb(particles, aabb); - _change_notify("visibility_rect"); update(); } -void GPUParticles2D::set_use_local_coordinates(bool p_enable) { +void GPUParticles2D::set_use_local_coordinates(bool p_enable) { local_coords = p_enable; RS::get_singleton()->particles_set_use_local_coordinates(particles, local_coords); set_notify_transform(!p_enable); @@ -117,9 +112,8 @@ void GPUParticles2D::set_use_local_coordinates(bool p_enable) { } void GPUParticles2D::_update_particle_emission_transform() { - Transform2D xf2d = get_global_transform(); - Transform xf; + Transform3D xf; xf.basis.set_axis(0, Vector3(xf2d.get_axis(0).x, xf2d.get_axis(0).y, 0)); xf.basis.set_axis(1, Vector3(xf2d.get_axis(1).x, xf2d.get_axis(1).y, 0)); xf.set_origin(Vector3(xf2d.get_origin().x, xf2d.get_origin().y, 0)); @@ -128,83 +122,143 @@ void GPUParticles2D::_update_particle_emission_transform() { } void GPUParticles2D::set_process_material(const Ref<Material> &p_material) { - process_material = p_material; Ref<ParticlesMaterial> pm = p_material; - if (pm.is_valid() && !pm->get_flag(ParticlesMaterial::FLAG_DISABLE_Z) && pm->get_gravity() == Vector3(0, -9.8, 0)) { + if (pm.is_valid() && !pm->get_particle_flag(ParticlesMaterial::PARTICLE_FLAG_DISABLE_Z) && pm->get_gravity() == Vector3(0, -9.8, 0)) { // Likely a new (3D) material, modify it to match 2D space - pm->set_flag(ParticlesMaterial::FLAG_DISABLE_Z, true); + pm->set_particle_flag(ParticlesMaterial::PARTICLE_FLAG_DISABLE_Z, true); pm->set_gravity(Vector3(0, 98, 0)); } RID material_rid; - if (process_material.is_valid()) + if (process_material.is_valid()) { material_rid = process_material->get_rid(); + } RS::get_singleton()->particles_set_process_material(particles, material_rid); - update_configuration_warning(); + update_configuration_warnings(); +} + +void GPUParticles2D::set_trail_enabled(bool p_enabled) { + trail_enabled = p_enabled; + RS::get_singleton()->particles_set_trails(particles, trail_enabled, trail_length); + update_configuration_warnings(); + update(); + + RS::get_singleton()->particles_set_transform_align(particles, p_enabled ? RS::PARTICLES_TRANSFORM_ALIGN_Y_TO_VELOCITY : RS::PARTICLES_TRANSFORM_ALIGN_DISABLED); +} + +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); + update(); +} + +void GPUParticles2D::set_trail_sections(int p_sections) { + ERR_FAIL_COND(p_sections < 2); + ERR_FAIL_COND(p_sections > 128); + + 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); + + trail_section_subdivisions = p_subdivisions; + update(); +} + +bool GPUParticles2D::is_trail_enabled() const { + return trail_enabled; +} + +double GPUParticles2D::get_trail_length() const { + return trail_length; +} + +void GPUParticles2D::_update_collision_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 + } + + RS::get_singleton()->particles_set_collision_base_size(particles, csize); } -void GPUParticles2D::set_speed_scale(float p_scale) { +void GPUParticles2D::set_collision_base_size(real_t p_size) { + collision_base_size = p_size; + _update_collision_size(); +} +real_t GPUParticles2D::get_collision_base_size() const { + return collision_base_size; +} + +void GPUParticles2D::set_speed_scale(double p_scale) { speed_scale = p_scale; RS::get_singleton()->particles_set_speed_scale(particles, p_scale); } bool GPUParticles2D::is_emitting() const { - return RS::get_singleton()->particles_get_emitting(particles); } -int GPUParticles2D::get_amount() const { +int GPUParticles2D::get_amount() const { return amount; } -float GPUParticles2D::get_lifetime() const { +double GPUParticles2D::get_lifetime() const { return lifetime; } -bool GPUParticles2D::get_one_shot() const { +int GPUParticles2D::get_trail_sections() const { + return trail_sections; +} +int GPUParticles2D::get_trail_section_subdivisions() const { + return trail_section_subdivisions; +} +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; } -Rect2 GPUParticles2D::get_visibility_rect() const { +Rect2 GPUParticles2D::get_visibility_rect() const { return visibility_rect; } -bool GPUParticles2D::get_use_local_coordinates() const { +bool GPUParticles2D::get_use_local_coordinates() const { return local_coords; } -Ref<Material> GPUParticles2D::get_process_material() const { +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; } void GPUParticles2D::set_draw_order(DrawOrder p_order) { - draw_order = p_order; RS::get_singleton()->particles_set_draw_order(particles, RS::ParticlesDrawOrder(p_order)); } GPUParticles2D::DrawOrder GPUParticles2D::get_draw_order() const { - return draw_order; } @@ -226,30 +280,24 @@ bool GPUParticles2D::get_fractional_delta() const { return fractional_delta; } -String GPUParticles2D::get_configuration_warning() const { +TypedArray<String> GPUParticles2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); if (RenderingServer::get_singleton()->is_low_end()) { - return TTR("GPU-based particles are not supported by the GLES2 video driver.\nUse the CPUParticles2D node instead. You can use the \"Convert to CPUParticles2D\" option for this purpose."); + warnings.push_back(TTR("GPU-based particles are not supported by the GLES2 video driver.\nUse the CPUParticles2D node instead. You can use the \"Convert to CPUParticles2D\" option for this purpose.")); } - String warnings; - if (process_material.is_null()) { - if (warnings != String()) - warnings += "\n"; - warnings += "- " + TTR("A material to process the particles is not assigned, so no behavior is imprinted."); + warnings.push_back(TTR("A material to process the particles is not assigned, so no behavior is imprinted.")); } else { - CanvasItemMaterial *mat = Object::cast_to<CanvasItemMaterial>(get_material().ptr()); 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())) { - if (warnings != String()) - warnings += "\n"; - warnings += "- " + TTR("Particles2D animation requires the usage of a CanvasItemMaterial with \"Particles Animation\" enabled."); + warnings.push_back(TTR("Particles2D animation requires the usage of a CanvasItemMaterial with \"Particles Animation\" enabled.")); } } } @@ -258,7 +306,6 @@ String GPUParticles2D::get_configuration_warning() const { } Rect2 GPUParticles2D::capture_rect() const { - AABB aabb = RS::get_singleton()->particles_get_current_aabb(particles); Rect2 r; r.position.x = aabb.position.x; @@ -270,6 +317,7 @@ Rect2 GPUParticles2D::capture_rect() const { void GPUParticles2D::set_texture(const Ref<Texture2D> &p_texture) { texture = p_texture; + _update_collision_size(); update(); } @@ -277,16 +325,6 @@ Ref<Texture2D> GPUParticles2D::get_texture() const { return texture; } -void GPUParticles2D::set_normal_map(const Ref<Texture2D> &p_normal_map) { - - normal_map = p_normal_map; - update(); -} - -Ref<Texture2D> GPUParticles2D::get_normal_map() const { - return normal_map; -} - void GPUParticles2D::_validate_property(PropertyInfo &property) const { } @@ -296,21 +334,125 @@ void GPUParticles2D::restart() { } void GPUParticles2D::_notification(int p_what) { - if (p_what == NOTIFICATION_DRAW) { - RID texture_rid; - if (texture.is_valid()) + Size2 size; + if (texture.is_valid()) { texture_rid = texture->get_rid(); - RID normal_rid; - if (normal_map.is_valid()) - normal_rid = normal_map->get_rid(); + size = texture->get_size(); + } else { + size = Size2(1, 1); + } - RS::get_singleton()->canvas_item_add_particles(get_canvas_item(), particles, texture_rid, normal_rid); + if (trail_enabled) { + RS::get_singleton()->mesh_clear(mesh); + PackedVector2Array points; + PackedVector2Array uvs; + PackedInt32Array bone_indices; + PackedFloat32Array bone_weights; + PackedInt32Array indices; + + int total_segments = trail_sections * trail_section_subdivisions; + real_t depth = size.height * trail_sections; + + for (int j = 0; j <= total_segments; j++) { + real_t v = j; + v /= total_segments; + + real_t y = depth * v; + y = (depth * 0.5) - y; + + int bone = j / trail_section_subdivisions; + real_t blend = 1.0 - real_t(j % trail_section_subdivisions) / real_t(trail_section_subdivisions); + + real_t s = size.width; + + points.push_back(Vector2(-s * 0.5, 0)); + points.push_back(Vector2(+s * 0.5, 0)); + + uvs.push_back(Vector2(0, v)); + uvs.push_back(Vector2(1, v)); + + for (int i = 0; i < 2; i++) { + bone_indices.push_back(bone); + bone_indices.push_back(MIN(trail_sections, bone + 1)); + bone_indices.push_back(0); + bone_indices.push_back(0); + + bone_weights.push_back(blend); + bone_weights.push_back(1.0 - blend); + bone_weights.push_back(0); + bone_weights.push_back(0); + } + + if (j > 0) { + int base = j * 2 - 2; + indices.push_back(base + 0); + indices.push_back(base + 1); + indices.push_back(base + 2); + + indices.push_back(base + 1); + indices.push_back(base + 3); + indices.push_back(base + 2); + } + } -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint() && (this == get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->is_a_parent_of(this))) { + Array arr; + arr.resize(RS::ARRAY_MAX); + arr[RS::ARRAY_VERTEX] = points; + arr[RS::ARRAY_TEX_UV] = uvs; + arr[RS::ARRAY_BONES] = bone_indices; + arr[RS::ARRAY_WEIGHTS] = bone_weights; + arr[RS::ARRAY_INDEX] = indices; + + RS::get_singleton()->mesh_add_surface_from_arrays(mesh, RS::PRIMITIVE_TRIANGLES, arr, Array(), Dictionary(), RS::ARRAY_FLAG_USE_2D_VERTICES); + + Vector<Transform3D> xforms; + for (int i = 0; i <= trail_sections; i++) { + Transform3D xform; + /* + 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); + } + RS::get_singleton()->particles_set_trail_bind_poses(particles, xforms); + + } else { + RS::get_singleton()->mesh_clear(mesh); + Vector<Vector2> points; + points.resize(4); + points.write[0] = Vector2(-size.x / 2.0, -size.y / 2.0); + points.write[1] = Vector2(size.x / 2.0, -size.y / 2.0); + points.write[2] = Vector2(size.x / 2.0, size.y / 2.0); + points.write[3] = Vector2(-size.x / 2.0, size.y / 2.0); + Vector<Vector2> uvs; + uvs.resize(4); + uvs.write[0] = Vector2(0, 0); + uvs.write[1] = Vector2(1, 0); + uvs.write[2] = Vector2(1, 1); + uvs.write[3] = Vector2(0, 1); + Vector<int> indices; + indices.resize(6); + indices.write[0] = 0; + indices.write[1] = 1; + indices.write[2] = 2; + indices.write[3] = 0; + indices.write[4] = 2; + indices.write[5] = 3; + Array arr; + arr.resize(RS::ARRAY_MAX); + arr[RS::ARRAY_VERTEX] = points; + arr[RS::ARRAY_TEX_UV] = uvs; + arr[RS::ARRAY_INDEX] = indices; + + RS::get_singleton()->mesh_add_surface_from_arrays(mesh, RS::PRIMITIVE_TRIANGLES, arr, Array(), Dictionary(), RS::ARRAY_FLAG_USE_2D_VERTICES); + RS::get_singleton()->particles_set_trail_bind_poses(particles, Vector<Transform3D>()); + } + RS::get_singleton()->canvas_item_add_particles(get_canvas_item(), particles, texture_rid); + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint() && (this == get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->is_ancestor_of(this))) { draw_rect(visibility_rect, Color(0, 0.7, 0.9, 0.4), false); } #endif @@ -320,7 +462,6 @@ void GPUParticles2D::_notification(int p_what) { if (can_process()) { RS::get_singleton()->particles_set_speed_scale(particles, speed_scale); } else { - RS::get_singleton()->particles_set_speed_scale(particles, 0); } } @@ -330,16 +471,14 @@ void GPUParticles2D::_notification(int p_what) { } if (p_what == NOTIFICATION_INTERNAL_PROCESS) { - if (one_shot && !is_emitting()) { - _change_notify(); + notify_property_list_changed(); set_process_internal(false); } } } void GPUParticles2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_emitting", "emitting"), &GPUParticles2D::set_emitting); ClassDB::bind_method(D_METHOD("set_amount", "amount"), &GPUParticles2D::set_amount); ClassDB::bind_method(D_METHOD("set_lifetime", "secs"), &GPUParticles2D::set_lifetime); @@ -353,6 +492,7 @@ void GPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_fractional_delta", "enable"), &GPUParticles2D::set_fractional_delta); ClassDB::bind_method(D_METHOD("set_process_material", "material"), &GPUParticles2D::set_process_material); ClassDB::bind_method(D_METHOD("set_speed_scale", "scale"), &GPUParticles2D::set_speed_scale); + ClassDB::bind_method(D_METHOD("set_collision_base_size", "size"), &GPUParticles2D::set_collision_base_size); ClassDB::bind_method(D_METHOD("is_emitting"), &GPUParticles2D::is_emitting); ClassDB::bind_method(D_METHOD("get_amount"), &GPUParticles2D::get_amount); @@ -367,6 +507,7 @@ void GPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_fractional_delta"), &GPUParticles2D::get_fractional_delta); ClassDB::bind_method(D_METHOD("get_process_material"), &GPUParticles2D::get_process_material); ClassDB::bind_method(D_METHOD("get_speed_scale"), &GPUParticles2D::get_speed_scale); + ClassDB::bind_method(D_METHOD("get_collision_base_size"), &GPUParticles2D::get_collision_base_size); ClassDB::bind_method(D_METHOD("set_draw_order", "order"), &GPUParticles2D::set_draw_order); ClassDB::bind_method(D_METHOD("get_draw_order"), &GPUParticles2D::get_draw_order); @@ -374,15 +515,24 @@ void GPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_texture", "texture"), &GPUParticles2D::set_texture); ClassDB::bind_method(D_METHOD("get_texture"), &GPUParticles2D::get_texture); - ClassDB::bind_method(D_METHOD("set_normal_map", "texture"), &GPUParticles2D::set_normal_map); - ClassDB::bind_method(D_METHOD("get_normal_map"), &GPUParticles2D::get_normal_map); - ClassDB::bind_method(D_METHOD("capture_rect"), &GPUParticles2D::capture_rect); ClassDB::bind_method(D_METHOD("restart"), &GPUParticles2D::restart); + ClassDB::bind_method(D_METHOD("set_trail_enabled", "enabled"), &GPUParticles2D::set_trail_enabled); + ClassDB::bind_method(D_METHOD("set_trail_length", "secs"), &GPUParticles2D::set_trail_length); + + ClassDB::bind_method(D_METHOD("is_trail_enabled"), &GPUParticles2D::is_trail_enabled); + ClassDB::bind_method(D_METHOD("get_trail_length"), &GPUParticles2D::get_trail_length); + + ClassDB::bind_method(D_METHOD("set_trail_sections", "sections"), &GPUParticles2D::set_trail_sections); + ClassDB::bind_method(D_METHOD("get_trail_sections"), &GPUParticles2D::get_trail_sections); + + ClassDB::bind_method(D_METHOD("set_trail_section_subdivisions", "subdivisions"), &GPUParticles2D::set_trail_section_subdivisions); + ClassDB::bind_method(D_METHOD("get_trail_section_subdivisions"), &GPUParticles2D::get_trail_section_subdivisions); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_EXP_RANGE, "1,1000000,1"), "set_amount", "get_amount"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount"); ADD_GROUP("Time", ""); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater"), "set_lifetime", "get_lifetime"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot"); @@ -392,23 +542,34 @@ void GPUParticles2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_randomness_ratio", "get_randomness_ratio"); ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1"), "set_fixed_fps", "get_fixed_fps"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta"); + ADD_GROUP("Collision", "collision_"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_base_size", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater"), "set_collision_base_size", "get_collision_base_size"); ADD_GROUP("Drawing", ""); ADD_PROPERTY(PropertyInfo(Variant::RECT2, "visibility_rect"), "set_visibility_rect", "get_visibility_rect"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "local_coords"), "set_use_local_coordinates", "get_use_local_coordinates"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "draw_order", PROPERTY_HINT_ENUM, "Index,Lifetime"), "set_draw_order", "get_draw_order"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "draw_order", PROPERTY_HINT_ENUM, "Index,Lifetime,Reverse Lifetime"), "set_draw_order", "get_draw_order"); + ADD_GROUP("Trails", "trail_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "trail_enabled"), "set_trail_enabled", "is_trail_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "trail_length_secs", PROPERTY_HINT_RANGE, "0.01,10,0.01"), "set_trail_length", "get_trail_length"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "trail_sections", PROPERTY_HINT_RANGE, "2,128,1"), "set_trail_sections", "get_trail_sections"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "trail_section_subdivisions", PROPERTY_HINT_RANGE, "1,1024,1"), "set_trail_section_subdivisions", "get_trail_section_subdivisions"); ADD_GROUP("Process Material", "process_"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "process_material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,ParticlesMaterial"), "set_process_material", "get_process_material"); ADD_GROUP("Textures", ""); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "normal_map", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_normal_map", "get_normal_map"); BIND_ENUM_CONSTANT(DRAW_ORDER_INDEX); BIND_ENUM_CONSTANT(DRAW_ORDER_LIFETIME); + BIND_ENUM_CONSTANT(DRAW_ORDER_REVERSE_LIFETIME); } GPUParticles2D::GPUParticles2D() { - particles = RS::get_singleton()->particles_create(); + RS::get_singleton()->particles_set_mode(particles, RS::PARTICLES_MODE_2D); + + mesh = RS::get_singleton()->mesh_create(); + RS::get_singleton()->particles_set_draw_passes(particles, 1); + RS::get_singleton()->particles_set_draw_pass_mesh(particles, 0, mesh); one_shot = false; // Needed so that set_emitting doesn't access uninitialized values set_emitting(true); @@ -422,11 +583,13 @@ GPUParticles2D::GPUParticles2D() { set_randomness_ratio(0); set_visibility_rect(Rect2(Vector2(-100, -100), Vector2(200, 200))); set_use_local_coordinates(true); - set_draw_order(DRAW_ORDER_INDEX); + set_draw_order(DRAW_ORDER_LIFETIME); set_speed_scale(1); + set_fixed_fps(30); + set_collision_base_size(collision_base_size); } GPUParticles2D::~GPUParticles2D() { - RS::get_singleton()->free(particles); + RS::get_singleton()->free(mesh); } diff --git a/scene/2d/gpu_particles_2d.h b/scene/2d/gpu_particles_2d.h index 47951d76dc..d7eee461b4 100644 --- a/scene/2d/gpu_particles_2d.h +++ b/scene/2d/gpu_particles_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,9 +31,7 @@ #ifndef PARTICLES_2D_H #define PARTICLES_2D_H -#include "core/rid.h" #include "scene/2d/node_2d.h" -#include "scene/resources/texture.h" class GPUParticles2D : public Node2D { private: @@ -43,6 +41,7 @@ public: enum DrawOrder { DRAW_ORDER_INDEX, DRAW_ORDER_LIFETIME, + DRAW_ORDER_REVERSE_LIFETIME, }; private: @@ -50,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; @@ -65,39 +64,61 @@ private: DrawOrder draw_order; Ref<Texture2D> texture; - Ref<Texture2D> normal_map; void _update_particle_emission_transform(); + NodePath sub_emitter; + real_t collision_base_size = 1.0; + + bool trail_enabled = false; + double trail_length = 0.3; + int trail_sections = 8; + int trail_section_subdivisions = 4; + + RID mesh; + protected: static void _bind_methods(); - virtual void _validate_property(PropertyInfo &property) const; + virtual void _validate_property(PropertyInfo &property) const override; void _notification(int p_what); + void _update_collision_size(); + 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_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(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; + + real_t get_collision_base_size() const; + bool is_trail_enabled() const; + double get_trail_length() const; + int get_trail_sections() const; + int get_trail_section_subdivisions() const; void set_fixed_fps(int p_count); int get_fixed_fps() const; @@ -111,10 +132,7 @@ public: void set_texture(const Ref<Texture2D> &p_texture); Ref<Texture2D> get_texture() const; - void set_normal_map(const Ref<Texture2D> &p_normal_map); - Ref<Texture2D> get_normal_map() const; - - virtual String get_configuration_warning() const; + TypedArray<String> get_configuration_warnings() const override; void restart(); Rect2 capture_rect() const; diff --git a/scene/2d/joints_2d.cpp b/scene/2d/joints_2d.cpp index 4d49f4762f..4a6606256e 100644 --- a/scene/2d/joints_2d.cpp +++ b/scene/2d/joints_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,85 +30,138 @@ #include "joints_2d.h" -#include "core/engine.h" #include "physics_body_2d.h" -#include "servers/physics_server_2d.h" +#include "scene/scene_string_names.h" -void Joint2D::_update_joint(bool p_only_free) { - - if (joint.is_valid()) { - if (ba.is_valid() && bb.is_valid() && exclude_from_collision) - PhysicsServer2D::get_singleton()->joint_disable_collisions_between_bodies(joint, false); +void Joint2D::_disconnect_signals() { + Node *node_a = get_node_or_null(a); + PhysicsBody2D *body_a = Object::cast_to<PhysicsBody2D>(node_a); + if (body_a) { + body_a->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Joint2D::_body_exit_tree)); + } - PhysicsServer2D::get_singleton()->free(joint); - joint = RID(); - ba = RID(); - bb = RID(); + Node *node_b = get_node_or_null(b); + PhysicsBody2D *body_b = Object::cast_to<PhysicsBody2D>(node_b); + if (body_b) { + body_b->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Joint2D::_body_exit_tree)); } +} - if (p_only_free || !is_inside_tree()) - return; +void Joint2D::_body_exit_tree() { + _disconnect_signals(); + _update_joint(true); +} + +void Joint2D::_update_joint(bool p_only_free) { + if (ba.is_valid() && bb.is_valid() && exclude_from_collision) { + PhysicsServer2D::get_singleton()->joint_disable_collisions_between_bodies(joint, false); + } - Node *node_a = has_node(get_node_a()) ? get_node(get_node_a()) : (Node *)nullptr; - Node *node_b = has_node(get_node_b()) ? get_node(get_node_b()) : (Node *)nullptr; + ba = RID(); + bb = RID(); + configured = false; - if (!node_a || !node_b) + if (p_only_free || !is_inside_tree()) { + PhysicsServer2D::get_singleton()->joint_clear(joint); + warning = String(); + update_configuration_warnings(); return; + } + + Node *node_a = get_node_or_null(a); + Node *node_b = get_node_or_null(b); PhysicsBody2D *body_a = Object::cast_to<PhysicsBody2D>(node_a); PhysicsBody2D *body_b = Object::cast_to<PhysicsBody2D>(node_b); - if (!body_a || !body_b) - return; + if (node_a && !body_a && node_b && !body_b) { + warning = TTR("Node A and Node B must be PhysicsBody2Ds"); + } else if (node_a && !body_a) { + warning = TTR("Node A must be a PhysicsBody2D"); + } else if (node_b && !body_b) { + warning = TTR("Node B must be a PhysicsBody2D"); + } else if (!body_a || !body_b) { + warning = TTR("Joint is not connected to two PhysicsBody2Ds"); + } else if (body_a == body_b) { + warning = TTR("Node A and Node B must be different PhysicsBody2Ds"); + } else { + warning = String(); + } - joint = _configure_joint(body_a, body_b); + update_configuration_warnings(); - if (!joint.is_valid()) + if (!warning.is_empty()) { + PhysicsServer2D::get_singleton()->joint_clear(joint); return; + } + + if (body_a) { + body_a->force_update_transform(); + } + + if (body_b) { + body_b->force_update_transform(); + } + + configured = true; + + _configure_joint(joint, body_a, body_b); + + ERR_FAIL_COND_MSG(!joint.is_valid(), "Failed to configure the joint."); PhysicsServer2D::get_singleton()->get_singleton()->joint_set_param(joint, PhysicsServer2D::JOINT_PARAM_BIAS, bias); ba = body_a->get_rid(); bb = body_b->get_rid(); + body_a->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Joint2D::_body_exit_tree)); + body_b->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Joint2D::_body_exit_tree)); + PhysicsServer2D::get_singleton()->joint_disable_collisions_between_bodies(joint, exclude_from_collision); } void Joint2D::set_node_a(const NodePath &p_node_a) { - - if (a == p_node_a) + if (a == p_node_a) { return; + } + + if (joint.is_valid()) { + _disconnect_signals(); + } a = p_node_a; _update_joint(); } NodePath Joint2D::get_node_a() const { - return a; } void Joint2D::set_node_b(const NodePath &p_node_b) { - - if (b == p_node_b) + if (b == p_node_b) { return; + } + + if (joint.is_valid()) { + _disconnect_signals(); + } + b = p_node_b; _update_joint(); } -NodePath Joint2D::get_node_b() const { +NodePath Joint2D::get_node_b() const { return b; } void Joint2D::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_READY: { _update_joint(); } break; case NOTIFICATION_EXIT_TREE: { if (joint.is_valid()) { + _disconnect_signals(); _update_joint(true); } } break; @@ -116,21 +169,20 @@ void Joint2D::_notification(int p_what) { } void Joint2D::set_bias(real_t p_bias) { - bias = p_bias; - if (joint.is_valid()) + if (joint.is_valid()) { PhysicsServer2D::get_singleton()->get_singleton()->joint_set_param(joint, PhysicsServer2D::JOINT_PARAM_BIAS, bias); + } } real_t Joint2D::get_bias() const { - return bias; } void Joint2D::set_exclude_nodes_from_collision(bool p_enable) { - - if (exclude_from_collision == p_enable) + if (exclude_from_collision == p_enable) { return; + } _update_joint(true); exclude_from_collision = p_enable; @@ -138,12 +190,20 @@ void Joint2D::set_exclude_nodes_from_collision(bool p_enable) { } bool Joint2D::get_exclude_nodes_from_collision() const { - return exclude_from_collision; } -void Joint2D::_bind_methods() { +TypedArray<String> Joint2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node2D::get_configuration_warnings(); + + if (!warning.is_empty()) { + warnings.push_back(warning); + } + + return warnings; +} +void Joint2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_node_a", "node"), &Joint2D::set_node_a); ClassDB::bind_method(D_METHOD("get_node_a"), &Joint2D::get_node_a); @@ -156,16 +216,18 @@ void Joint2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_exclude_nodes_from_collision", "enable"), &Joint2D::set_exclude_nodes_from_collision); ClassDB::bind_method(D_METHOD("get_exclude_nodes_from_collision"), &Joint2D::get_exclude_nodes_from_collision); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "node_a", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CollisionObject2D"), "set_node_a", "get_node_a"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "node_b", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CollisionObject2D"), "set_node_b", "get_node_b"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "node_a", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "PhysicsBody2D"), "set_node_a", "get_node_a"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "node_b", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "PhysicsBody2D"), "set_node_b", "get_node_b"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bias", PROPERTY_HINT_RANGE, "0,0.9,0.001"), "set_bias", "get_bias"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_collision"), "set_exclude_nodes_from_collision", "get_exclude_nodes_from_collision"); } Joint2D::Joint2D() { + joint = PhysicsServer2D::get_singleton()->joint_create(); +} - bias = 0; - exclude_from_collision = true; +Joint2D::~Joint2D() { + PhysicsServer2D::get_singleton()->free(joint); } /////////////////////////////////////////////////////////////////////////////// @@ -173,12 +235,11 @@ Joint2D::Joint2D() { /////////////////////////////////////////////////////////////////////////////// void PinJoint2D::_notification(int p_what) { - switch (p_what) { case NOTIFICATION_DRAW: { - - if (!is_inside_tree()) + if (!is_inside_tree()) { break; + } if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) { break; @@ -190,37 +251,31 @@ void PinJoint2D::_notification(int p_what) { } } -RID PinJoint2D::_configure_joint(PhysicsBody2D *body_a, PhysicsBody2D *body_b) { - - RID pj = PhysicsServer2D::get_singleton()->pin_joint_create(get_global_transform().get_origin(), body_a->get_rid(), body_b ? body_b->get_rid() : RID()); - PhysicsServer2D::get_singleton()->pin_joint_set_param(pj, PhysicsServer2D::PIN_JOINT_SOFTNESS, softness); - return pj; +void PinJoint2D::_configure_joint(RID p_joint, PhysicsBody2D *body_a, PhysicsBody2D *body_b) { + 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); } void PinJoint2D::set_softness(real_t p_softness) { - softness = p_softness; update(); - if (get_joint().is_valid()) + if (is_configured()) { PhysicsServer2D::get_singleton()->pin_joint_set_param(get_joint(), PhysicsServer2D::PIN_JOINT_SOFTNESS, p_softness); + } } real_t PinJoint2D::get_softness() const { - return softness; } void PinJoint2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_softness", "softness"), &PinJoint2D::set_softness); ClassDB::bind_method(D_METHOD("get_softness"), &PinJoint2D::get_softness); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "softness", PROPERTY_HINT_EXP_RANGE, "0.00,16,0.01"), "set_softness", "get_softness"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "softness", PROPERTY_HINT_RANGE, "0.00,16,0.01,exp"), "set_softness", "get_softness"); } PinJoint2D::PinJoint2D() { - - softness = 0; } /////////////////////////////////////////////////////////////////////////////// @@ -228,11 +283,11 @@ PinJoint2D::PinJoint2D() { /////////////////////////////////////////////////////////////////////////////// void GrooveJoint2D::_notification(int p_what) { - switch (p_what) { case NOTIFICATION_DRAW: { - if (!is_inside_tree()) + if (!is_inside_tree()) { break; + } if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) { break; @@ -246,53 +301,44 @@ void GrooveJoint2D::_notification(int p_what) { } } -RID GrooveJoint2D::_configure_joint(PhysicsBody2D *body_a, PhysicsBody2D *body_b) { - +void GrooveJoint2D::_configure_joint(RID p_joint, PhysicsBody2D *body_a, PhysicsBody2D *body_b) { Transform2D gt = get_global_transform(); Vector2 groove_A1 = gt.get_origin(); Vector2 groove_A2 = gt.xform(Vector2(0, length)); Vector2 anchor_B = gt.xform(Vector2(0, initial_offset)); - return PhysicsServer2D::get_singleton()->groove_joint_create(groove_A1, groove_A2, anchor_B, body_a->get_rid(), body_b->get_rid()); + PhysicsServer2D::get_singleton()->joint_make_groove(p_joint, groove_A1, groove_A2, anchor_B, body_a->get_rid(), body_b->get_rid()); } void GrooveJoint2D::set_length(real_t p_length) { - length = p_length; update(); } real_t GrooveJoint2D::get_length() const { - return length; } void GrooveJoint2D::set_initial_offset(real_t p_initial_offset) { - initial_offset = p_initial_offset; update(); } real_t GrooveJoint2D::get_initial_offset() const { - return initial_offset; } void GrooveJoint2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_length", "length"), &GrooveJoint2D::set_length); ClassDB::bind_method(D_METHOD("get_length"), &GrooveJoint2D::get_length); ClassDB::bind_method(D_METHOD("set_initial_offset", "offset"), &GrooveJoint2D::set_initial_offset); ClassDB::bind_method(D_METHOD("get_initial_offset"), &GrooveJoint2D::get_initial_offset); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_EXP_RANGE, "1,65535,1"), "set_length", "get_length"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "initial_offset", PROPERTY_HINT_EXP_RANGE, "1,65535,1"), "set_initial_offset", "get_initial_offset"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "1,65535,1,exp"), "set_length", "get_length"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "initial_offset", PROPERTY_HINT_RANGE, "1,65535,1,exp"), "set_initial_offset", "get_initial_offset"); } GrooveJoint2D::GrooveJoint2D() { - - length = 50; - initial_offset = 25; } /////////////////////////////////////////////////////////////////////////////// @@ -300,12 +346,11 @@ GrooveJoint2D::GrooveJoint2D() { /////////////////////////////////////////////////////////////////////////////// void DampedSpringJoint2D::_notification(int p_what) { - switch (p_what) { case NOTIFICATION_DRAW: { - - if (!is_inside_tree()) + if (!is_inside_tree()) { break; + } if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) { break; @@ -318,73 +363,65 @@ void DampedSpringJoint2D::_notification(int p_what) { } } -RID DampedSpringJoint2D::_configure_joint(PhysicsBody2D *body_a, PhysicsBody2D *body_b) { - +void DampedSpringJoint2D::_configure_joint(RID p_joint, PhysicsBody2D *body_a, PhysicsBody2D *body_b) { Transform2D gt = get_global_transform(); Vector2 anchor_A = gt.get_origin(); Vector2 anchor_B = gt.xform(Vector2(0, length)); - RID dsj = PhysicsServer2D::get_singleton()->damped_spring_joint_create(anchor_A, anchor_B, body_a->get_rid(), body_b->get_rid()); - if (rest_length) - PhysicsServer2D::get_singleton()->damped_string_joint_set_param(dsj, PhysicsServer2D::DAMPED_STRING_REST_LENGTH, rest_length); - PhysicsServer2D::get_singleton()->damped_string_joint_set_param(dsj, PhysicsServer2D::DAMPED_STRING_STIFFNESS, stiffness); - PhysicsServer2D::get_singleton()->damped_string_joint_set_param(dsj, PhysicsServer2D::DAMPED_STRING_DAMPING, damping); - - return dsj; + PhysicsServer2D::get_singleton()->joint_make_damped_spring(p_joint, anchor_A, anchor_B, body_a->get_rid(), body_b->get_rid()); + if (rest_length) { + PhysicsServer2D::get_singleton()->damped_spring_joint_set_param(p_joint, PhysicsServer2D::DAMPED_SPRING_REST_LENGTH, rest_length); + } + PhysicsServer2D::get_singleton()->damped_spring_joint_set_param(p_joint, PhysicsServer2D::DAMPED_SPRING_STIFFNESS, stiffness); + PhysicsServer2D::get_singleton()->damped_spring_joint_set_param(p_joint, PhysicsServer2D::DAMPED_SPRING_DAMPING, damping); } void DampedSpringJoint2D::set_length(real_t p_length) { - length = p_length; update(); } real_t DampedSpringJoint2D::get_length() const { - return length; } void DampedSpringJoint2D::set_rest_length(real_t p_rest_length) { - rest_length = p_rest_length; update(); - if (get_joint().is_valid()) - PhysicsServer2D::get_singleton()->damped_string_joint_set_param(get_joint(), PhysicsServer2D::DAMPED_STRING_REST_LENGTH, p_rest_length ? p_rest_length : length); + if (is_configured()) { + PhysicsServer2D::get_singleton()->damped_spring_joint_set_param(get_joint(), PhysicsServer2D::DAMPED_SPRING_REST_LENGTH, p_rest_length ? p_rest_length : length); + } } real_t DampedSpringJoint2D::get_rest_length() const { - return rest_length; } void DampedSpringJoint2D::set_stiffness(real_t p_stiffness) { - stiffness = p_stiffness; update(); - if (get_joint().is_valid()) - PhysicsServer2D::get_singleton()->damped_string_joint_set_param(get_joint(), PhysicsServer2D::DAMPED_STRING_STIFFNESS, p_stiffness); + if (is_configured()) { + PhysicsServer2D::get_singleton()->damped_spring_joint_set_param(get_joint(), PhysicsServer2D::DAMPED_SPRING_STIFFNESS, p_stiffness); + } } real_t DampedSpringJoint2D::get_stiffness() const { - return stiffness; } void DampedSpringJoint2D::set_damping(real_t p_damping) { - damping = p_damping; update(); - if (get_joint().is_valid()) - PhysicsServer2D::get_singleton()->damped_string_joint_set_param(get_joint(), PhysicsServer2D::DAMPED_STRING_DAMPING, p_damping); + if (is_configured()) { + PhysicsServer2D::get_singleton()->damped_spring_joint_set_param(get_joint(), PhysicsServer2D::DAMPED_SPRING_DAMPING, p_damping); + } } real_t DampedSpringJoint2D::get_damping() const { - return damping; } void DampedSpringJoint2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_length", "length"), &DampedSpringJoint2D::set_length); ClassDB::bind_method(D_METHOD("get_length"), &DampedSpringJoint2D::get_length); ClassDB::bind_method(D_METHOD("set_rest_length", "rest_length"), &DampedSpringJoint2D::set_rest_length); @@ -394,16 +431,11 @@ void DampedSpringJoint2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_damping", "damping"), &DampedSpringJoint2D::set_damping); ClassDB::bind_method(D_METHOD("get_damping"), &DampedSpringJoint2D::get_damping); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_EXP_RANGE, "1,65535,1"), "set_length", "get_length"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rest_length", PROPERTY_HINT_EXP_RANGE, "0,65535,1"), "set_rest_length", "get_rest_length"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "stiffness", PROPERTY_HINT_EXP_RANGE, "0.1,64,0.1"), "set_stiffness", "get_stiffness"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping", PROPERTY_HINT_EXP_RANGE, "0.01,16,0.01"), "set_damping", "get_damping"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "1,65535,1,exp"), "set_length", "get_length"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rest_length", PROPERTY_HINT_RANGE, "0,65535,1,exp"), "set_rest_length", "get_rest_length"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "stiffness", PROPERTY_HINT_RANGE, "0.1,64,0.1,exp"), "set_stiffness", "get_stiffness"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping", PROPERTY_HINT_RANGE, "0.01,16,0.01,exp"), "set_damping", "get_damping"); } DampedSpringJoint2D::DampedSpringJoint2D() { - - length = 50; - rest_length = 0; - stiffness = 20; - damping = 1; } diff --git a/scene/2d/joints_2d.h b/scene/2d/joints_2d.h index f1750e56b6..dc5a08f815 100644 --- a/scene/2d/joints_2d.h +++ b/scene/2d/joints_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -36,7 +36,6 @@ class PhysicsBody2D; class Joint2D : public Node2D { - GDCLASS(Joint2D, Node2D); RID joint; @@ -44,19 +43,27 @@ class Joint2D : public Node2D { NodePath a; NodePath b; - real_t bias; + real_t bias = 0.0; - bool exclude_from_collision; + bool exclude_from_collision = true; + bool configured = false; + String warning; protected: + void _disconnect_signals(); + void _body_exit_tree(); void _update_joint(bool p_only_free = false); void _notification(int p_what); - virtual RID _configure_joint(PhysicsBody2D *body_a, PhysicsBody2D *body_b) = 0; + virtual void _configure_joint(RID p_joint, PhysicsBody2D *body_a, PhysicsBody2D *body_b) = 0; static void _bind_methods(); + _FORCE_INLINE_ bool is_configured() const { return configured; } + public: + virtual TypedArray<String> get_configuration_warnings() const override; + void set_node_a(const NodePath &p_node_a); NodePath get_node_a() const; @@ -71,17 +78,17 @@ public: RID get_joint() const { return joint; } Joint2D(); + ~Joint2D(); }; class PinJoint2D : public Joint2D { - GDCLASS(PinJoint2D, Joint2D); - real_t softness; + real_t softness = 0.0; protected: void _notification(int p_what); - virtual RID _configure_joint(PhysicsBody2D *body_a, PhysicsBody2D *body_b); + virtual void _configure_joint(RID p_joint, PhysicsBody2D *body_a, PhysicsBody2D *body_b) override; static void _bind_methods(); public: @@ -92,15 +99,14 @@ public: }; class GrooveJoint2D : public Joint2D { - GDCLASS(GrooveJoint2D, Joint2D); - real_t length; - real_t initial_offset; + real_t length = 50.0; + real_t initial_offset = 25.0; protected: void _notification(int p_what); - virtual RID _configure_joint(PhysicsBody2D *body_a, PhysicsBody2D *body_b); + virtual void _configure_joint(RID p_joint, PhysicsBody2D *body_a, PhysicsBody2D *body_b) override; static void _bind_methods(); public: @@ -114,17 +120,16 @@ public: }; class DampedSpringJoint2D : public Joint2D { - GDCLASS(DampedSpringJoint2D, Joint2D); - real_t stiffness; - real_t damping; - real_t rest_length; - real_t length; + real_t stiffness = 20.0; + real_t damping = 1.0; + real_t rest_length = 0.0; + real_t length = 50.0; protected: void _notification(int p_what); - virtual RID _configure_joint(PhysicsBody2D *body_a, PhysicsBody2D *body_b); + virtual void _configure_joint(RID p_joint, PhysicsBody2D *body_a, PhysicsBody2D *body_b) override; static void _bind_methods(); public: diff --git a/scene/2d/light_2d.cpp b/scene/2d/light_2d.cpp index b3d54b81f8..1853b3428c 100644 --- a/scene/2d/light_2d.cpp +++ b/scene/2d/light_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,59 +30,10 @@ #include "light_2d.h" -#include "core/engine.h" -#include "servers/rendering_server.h" - -#ifdef TOOLS_ENABLED -Dictionary Light2D::_edit_get_state() const { - Dictionary state = Node2D::_edit_get_state(); - state["offset"] = get_texture_offset(); - return state; -} - -void Light2D::_edit_set_state(const Dictionary &p_state) { - Node2D::_edit_set_state(p_state); - set_texture_offset(p_state["offset"]); -} - -void Light2D::_edit_set_pivot(const Point2 &p_pivot) { - set_position(get_transform().xform(p_pivot)); - set_texture_offset(get_texture_offset() - p_pivot); -} - -Point2 Light2D::_edit_get_pivot() const { - return Vector2(); -} - -bool Light2D::_edit_use_pivot() const { - return true; -} - -Rect2 Light2D::_edit_get_rect() const { - if (texture.is_null()) - return Rect2(); - - Size2 s = texture->get_size() * _scale; - return Rect2(texture_offset - s / 2.0, s); -} - -bool Light2D::_edit_use_rect() const { - return !texture.is_null(); -} -#endif - -Rect2 Light2D::get_anchorable_rect() const { - if (texture.is_null()) - return Rect2(); - - Size2 s = texture->get_size() * _scale; - return Rect2(texture_offset - s / 2.0, s); -} - void Light2D::_update_light_visibility() { - - if (!is_inside_tree()) + if (!is_inside_tree()) { return; + } bool editor_ok = true; @@ -104,198 +55,114 @@ void Light2D::_update_light_visibility() { } void Light2D::set_enabled(bool p_enabled) { - enabled = p_enabled; _update_light_visibility(); } bool Light2D::is_enabled() const { - return enabled; } void Light2D::set_editor_only(bool p_editor_only) { - editor_only = p_editor_only; _update_light_visibility(); } bool Light2D::is_editor_only() const { - return editor_only; } -void Light2D::set_texture(const Ref<Texture2D> &p_texture) { - - texture = p_texture; - if (texture.is_valid()) - RS::get_singleton()->canvas_light_set_texture(canvas_light, texture->get_rid()); - else - RS::get_singleton()->canvas_light_set_texture(canvas_light, RID()); - - update_configuration_warning(); -} - -Ref<Texture2D> Light2D::get_texture() const { - - return texture; -} - -void Light2D::set_texture_offset(const Vector2 &p_offset) { - - texture_offset = p_offset; - RS::get_singleton()->canvas_light_set_texture_offset(canvas_light, texture_offset); - item_rect_changed(); - _change_notify("offset"); -} - -Vector2 Light2D::get_texture_offset() const { - - return texture_offset; -} - void Light2D::set_color(const Color &p_color) { - color = p_color; RS::get_singleton()->canvas_light_set_color(canvas_light, color); } -Color Light2D::get_color() const { +Color Light2D::get_color() const { return color; } -void Light2D::set_height(float p_height) { - +void Light2D::set_height(real_t p_height) { height = p_height; RS::get_singleton()->canvas_light_set_height(canvas_light, height); } -float Light2D::get_height() const { - +real_t Light2D::get_height() const { return height; } -void Light2D::set_energy(float p_energy) { - +void Light2D::set_energy(real_t p_energy) { energy = p_energy; RS::get_singleton()->canvas_light_set_energy(canvas_light, energy); } -float Light2D::get_energy() const { - +real_t Light2D::get_energy() const { return energy; } -void Light2D::set_texture_scale(float p_scale) { - - _scale = p_scale; - // Avoid having 0 scale values, can lead to errors in physics and rendering. - if (_scale == 0) { - _scale = CMP_EPSILON; - } - RS::get_singleton()->canvas_light_set_scale(canvas_light, _scale); - item_rect_changed(); -} - -float Light2D::get_texture_scale() const { - - return _scale; -} - void Light2D::set_z_range_min(int p_min_z) { - z_min = p_min_z; RS::get_singleton()->canvas_light_set_z_range(canvas_light, z_min, z_max); } -int Light2D::get_z_range_min() const { +int Light2D::get_z_range_min() const { return z_min; } void Light2D::set_z_range_max(int p_max_z) { - z_max = p_max_z; RS::get_singleton()->canvas_light_set_z_range(canvas_light, z_min, z_max); } -int Light2D::get_z_range_max() const { +int Light2D::get_z_range_max() const { return z_max; } void Light2D::set_layer_range_min(int p_min_layer) { - layer_min = p_min_layer; RS::get_singleton()->canvas_light_set_layer_range(canvas_light, layer_min, layer_max); } -int Light2D::get_layer_range_min() const { +int Light2D::get_layer_range_min() const { return layer_min; } void Light2D::set_layer_range_max(int p_max_layer) { - layer_max = p_max_layer; RS::get_singleton()->canvas_light_set_layer_range(canvas_light, layer_min, layer_max); } -int Light2D::get_layer_range_max() const { +int Light2D::get_layer_range_max() const { return layer_max; } void Light2D::set_item_cull_mask(int p_mask) { - item_mask = p_mask; RS::get_singleton()->canvas_light_set_item_cull_mask(canvas_light, item_mask); } int Light2D::get_item_cull_mask() const { - return item_mask; } void Light2D::set_item_shadow_cull_mask(int p_mask) { - item_shadow_mask = p_mask; RS::get_singleton()->canvas_light_set_item_shadow_cull_mask(canvas_light, item_shadow_mask); } int Light2D::get_item_shadow_cull_mask() const { - return item_shadow_mask; } -void Light2D::set_mode(Mode p_mode) { - - mode = p_mode; - RS::get_singleton()->canvas_light_set_mode(canvas_light, RS::CanvasLightMode(p_mode)); -} - -Light2D::Mode Light2D::get_mode() const { - - return mode; -} - void Light2D::set_shadow_enabled(bool p_enabled) { - shadow = p_enabled; RS::get_singleton()->canvas_light_set_shadow_enabled(canvas_light, shadow); + notify_property_list_changed(); } -bool Light2D::is_shadow_enabled() const { +bool Light2D::is_shadow_enabled() const { return shadow; } -void Light2D::set_shadow_buffer_size(int p_size) { - - shadow_buffer_size = p_size; - RS::get_singleton()->canvas_light_set_shadow_buffer_size(canvas_light, shadow_buffer_size); -} - -int Light2D::get_shadow_buffer_size() const { - - return shadow_buffer_size; -} - void Light2D::set_shadow_filter(ShadowFilter p_filter) { ERR_FAIL_INDEX(p_filter, SHADOW_FILTER_MAX); shadow_filter = p_filter; @@ -303,7 +170,6 @@ void Light2D::set_shadow_filter(ShadowFilter p_filter) { } Light2D::ShadowFilter Light2D::get_shadow_filter() const { - return shadow_filter; } @@ -316,76 +182,62 @@ Color Light2D::get_shadow_color() const { return shadow_color; } -void Light2D::_notification(int p_what) { +void Light2D::set_blend_mode(BlendMode p_mode) { + blend_mode = p_mode; + RS::get_singleton()->canvas_light_set_blend_mode(_get_light(), RS::CanvasLightBlendMode(p_mode)); +} - if (p_what == NOTIFICATION_ENTER_TREE) { +Light2D::BlendMode Light2D::get_blend_mode() const { + return blend_mode; +} +void Light2D::_notification(int p_what) { + if (p_what == NOTIFICATION_ENTER_TREE) { RS::get_singleton()->canvas_light_attach_to_canvas(canvas_light, get_canvas()); _update_light_visibility(); } if (p_what == NOTIFICATION_TRANSFORM_CHANGED) { - RS::get_singleton()->canvas_light_set_transform(canvas_light, get_global_transform()); } if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - _update_light_visibility(); } if (p_what == NOTIFICATION_EXIT_TREE) { - RS::get_singleton()->canvas_light_attach_to_canvas(canvas_light, RID()); _update_light_visibility(); } } -String Light2D::get_configuration_warning() const { - - if (!texture.is_valid()) { - return TTR("A texture with the shape of the light must be supplied to the \"Texture\" property."); - } - - return String(); -} - -void Light2D::set_shadow_smooth(float p_amount) { - +void Light2D::set_shadow_smooth(real_t p_amount) { shadow_smooth = p_amount; RS::get_singleton()->canvas_light_set_shadow_smooth(canvas_light, shadow_smooth); } -float Light2D::get_shadow_smooth() const { - +real_t Light2D::get_shadow_smooth() const { return shadow_smooth; } -void Light2D::_bind_methods() { +void Light2D::_validate_property(PropertyInfo &property) const { + if (!shadow && (property.name == "shadow_color" || property.name == "shadow_filter" || property.name == "shadow_filter_smooth" || property.name == "shadow_item_cull_mask")) { + property.usage = PROPERTY_USAGE_NOEDITOR; + } +} +void Light2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &Light2D::set_enabled); ClassDB::bind_method(D_METHOD("is_enabled"), &Light2D::is_enabled); ClassDB::bind_method(D_METHOD("set_editor_only", "editor_only"), &Light2D::set_editor_only); ClassDB::bind_method(D_METHOD("is_editor_only"), &Light2D::is_editor_only); - ClassDB::bind_method(D_METHOD("set_texture", "texture"), &Light2D::set_texture); - ClassDB::bind_method(D_METHOD("get_texture"), &Light2D::get_texture); - - ClassDB::bind_method(D_METHOD("set_texture_offset", "texture_offset"), &Light2D::set_texture_offset); - ClassDB::bind_method(D_METHOD("get_texture_offset"), &Light2D::get_texture_offset); - ClassDB::bind_method(D_METHOD("set_color", "color"), &Light2D::set_color); ClassDB::bind_method(D_METHOD("get_color"), &Light2D::get_color); - ClassDB::bind_method(D_METHOD("set_height", "height"), &Light2D::set_height); - ClassDB::bind_method(D_METHOD("get_height"), &Light2D::get_height); - ClassDB::bind_method(D_METHOD("set_energy", "energy"), &Light2D::set_energy); ClassDB::bind_method(D_METHOD("get_energy"), &Light2D::get_energy); - ClassDB::bind_method(D_METHOD("set_texture_scale", "texture_scale"), &Light2D::set_texture_scale); - ClassDB::bind_method(D_METHOD("get_texture_scale"), &Light2D::get_texture_scale); - ClassDB::bind_method(D_METHOD("set_z_range_min", "z"), &Light2D::set_z_range_min); ClassDB::bind_method(D_METHOD("get_z_range_min"), &Light2D::get_z_range_min); @@ -404,15 +256,9 @@ void Light2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_item_shadow_cull_mask", "item_shadow_cull_mask"), &Light2D::set_item_shadow_cull_mask); ClassDB::bind_method(D_METHOD("get_item_shadow_cull_mask"), &Light2D::get_item_shadow_cull_mask); - ClassDB::bind_method(D_METHOD("set_mode", "mode"), &Light2D::set_mode); - ClassDB::bind_method(D_METHOD("get_mode"), &Light2D::get_mode); - ClassDB::bind_method(D_METHOD("set_shadow_enabled", "enabled"), &Light2D::set_shadow_enabled); ClassDB::bind_method(D_METHOD("is_shadow_enabled"), &Light2D::is_shadow_enabled); - ClassDB::bind_method(D_METHOD("set_shadow_buffer_size", "size"), &Light2D::set_shadow_buffer_size); - ClassDB::bind_method(D_METHOD("get_shadow_buffer_size"), &Light2D::get_shadow_buffer_size); - ClassDB::bind_method(D_METHOD("set_shadow_smooth", "smooth"), &Light2D::set_shadow_smooth); ClassDB::bind_method(D_METHOD("get_shadow_smooth"), &Light2D::get_shadow_smooth); @@ -422,16 +268,18 @@ void Light2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_shadow_color", "shadow_color"), &Light2D::set_shadow_color); ClassDB::bind_method(D_METHOD("get_shadow_color"), &Light2D::get_shadow_color); + ClassDB::bind_method(D_METHOD("set_blend_mode", "mode"), &Light2D::set_blend_mode); + ClassDB::bind_method(D_METHOD("get_blend_mode"), &Light2D::get_blend_mode); + + ClassDB::bind_method(D_METHOD("set_height", "height"), &Light2D::set_height); + ClassDB::bind_method(D_METHOD("get_height"), &Light2D::get_height); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editor_only"), "set_editor_only", "is_editor_only"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_texture_offset", "get_texture_offset"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "texture_scale", PROPERTY_HINT_RANGE, "0.01,50,0.01"), "set_texture_scale", "get_texture_scale"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "energy", PROPERTY_HINT_RANGE, "0,16,0.01,or_greater"), "set_energy", "get_energy"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Add,Sub,Mix,Mask"), "set_mode", "get_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_mode", PROPERTY_HINT_ENUM, "Add,Subtract,Mix"), "set_blend_mode", "get_blend_mode"); ADD_GROUP("Range", "range_"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "range_height", PROPERTY_HINT_RANGE, "-2048,2048,0.1,or_lesser,or_greater"), "set_height", "get_height"); ADD_PROPERTY(PropertyInfo(Variant::INT, "range_z_min", PROPERTY_HINT_RANGE, itos(RS::CANVAS_ITEM_Z_MIN) + "," + itos(RS::CANVAS_ITEM_Z_MAX) + ",1"), "set_z_range_min", "get_z_range_min"); ADD_PROPERTY(PropertyInfo(Variant::INT, "range_z_max", PROPERTY_HINT_RANGE, itos(RS::CANVAS_ITEM_Z_MIN) + "," + itos(RS::CANVAS_ITEM_Z_MAX) + ",1"), "set_z_range_max", "get_z_range_max"); ADD_PROPERTY(PropertyInfo(Variant::INT, "range_layer_min", PROPERTY_HINT_RANGE, "-512,512,1"), "set_layer_range_min", "get_layer_range_min"); @@ -441,47 +289,168 @@ void Light2D::_bind_methods() { ADD_GROUP("Shadow", "shadow_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shadow_enabled"), "set_shadow_enabled", "is_shadow_enabled"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "shadow_color"), "set_shadow_color", "get_shadow_color"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "shadow_buffer_size", PROPERTY_HINT_RANGE, "32,16384,1"), "set_shadow_buffer_size", "get_shadow_buffer_size"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "shadow_filter", PROPERTY_HINT_ENUM, "None,PCF5,PCF13"), "set_shadow_filter", "get_shadow_filter"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "shadow_filter", PROPERTY_HINT_ENUM, "None (Fast),PCF5 (Average),PCF13 (Slow)"), "set_shadow_filter", "get_shadow_filter"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "shadow_filter_smooth", PROPERTY_HINT_RANGE, "0,64,0.1"), "set_shadow_smooth", "get_shadow_smooth"); ADD_PROPERTY(PropertyInfo(Variant::INT, "shadow_item_cull_mask", PROPERTY_HINT_LAYERS_2D_RENDER), "set_item_shadow_cull_mask", "get_item_shadow_cull_mask"); - BIND_ENUM_CONSTANT(MODE_ADD); - BIND_ENUM_CONSTANT(MODE_SUB); - BIND_ENUM_CONSTANT(MODE_MIX); - BIND_ENUM_CONSTANT(MODE_MASK); - BIND_ENUM_CONSTANT(SHADOW_FILTER_NONE); BIND_ENUM_CONSTANT(SHADOW_FILTER_PCF5); BIND_ENUM_CONSTANT(SHADOW_FILTER_PCF13); + + BIND_ENUM_CONSTANT(BLEND_MODE_ADD); + BIND_ENUM_CONSTANT(BLEND_MODE_SUB); + BIND_ENUM_CONSTANT(BLEND_MODE_MIX); } Light2D::Light2D() { - canvas_light = RenderingServer::get_singleton()->canvas_light_create(); - enabled = true; - editor_only = false; - shadow = false; - color = Color(1, 1, 1); - height = 0; - _scale = 1.0; - z_min = -1024; - z_max = 1024; - layer_min = 0; - layer_max = 0; - item_mask = 1; - item_shadow_mask = 1; - mode = MODE_ADD; - shadow_buffer_size = 2048; - energy = 1.0; - shadow_color = Color(0, 0, 0, 0); - shadow_filter = SHADOW_FILTER_NONE; - shadow_smooth = 0; - set_notify_transform(true); } Light2D::~Light2D() { - RenderingServer::get_singleton()->free(canvas_light); } + +////////////////////////////// + +#ifdef TOOLS_ENABLED + +Dictionary PointLight2D::_edit_get_state() const { + Dictionary state = Node2D::_edit_get_state(); + state["offset"] = get_texture_offset(); + return state; +} + +void PointLight2D::_edit_set_state(const Dictionary &p_state) { + Node2D::_edit_set_state(p_state); + set_texture_offset(p_state["offset"]); +} + +void PointLight2D::_edit_set_pivot(const Point2 &p_pivot) { + set_position(get_transform().xform(p_pivot)); + set_texture_offset(get_texture_offset() - p_pivot); +} + +Point2 PointLight2D::_edit_get_pivot() const { + return Vector2(); +} + +bool PointLight2D::_edit_use_pivot() const { + return true; +} + +Rect2 PointLight2D::_edit_get_rect() const { + if (texture.is_null()) { + return Rect2(); + } + + Size2 s = texture->get_size() * _scale; + return Rect2(texture_offset - s / 2.0, s); +} + +bool PointLight2D::_edit_use_rect() const { + return !texture.is_null(); +} +#endif + +Rect2 PointLight2D::get_anchorable_rect() const { + if (texture.is_null()) { + return Rect2(); + } + + Size2 s = texture->get_size() * _scale; + return Rect2(texture_offset - s / 2.0, s); +} + +void PointLight2D::set_texture(const Ref<Texture2D> &p_texture) { + texture = p_texture; + if (texture.is_valid()) { + RS::get_singleton()->canvas_light_set_texture(_get_light(), texture->get_rid()); + } else { + RS::get_singleton()->canvas_light_set_texture(_get_light(), RID()); + } + + update_configuration_warnings(); +} + +Ref<Texture2D> PointLight2D::get_texture() const { + return texture; +} + +void PointLight2D::set_texture_offset(const Vector2 &p_offset) { + texture_offset = p_offset; + RS::get_singleton()->canvas_light_set_texture_offset(_get_light(), texture_offset); + item_rect_changed(); +} + +Vector2 PointLight2D::get_texture_offset() const { + return texture_offset; +} + +TypedArray<String> PointLight2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); + + if (!texture.is_valid()) { + warnings.push_back(TTR("A texture with the shape of the light must be supplied to the \"Texture\" property.")); + } + + return warnings; +} + +void PointLight2D::set_texture_scale(real_t p_scale) { + _scale = p_scale; + // Avoid having 0 scale values, can lead to errors in physics and rendering. + if (_scale == 0) { + _scale = CMP_EPSILON; + } + RS::get_singleton()->canvas_light_set_texture_scale(_get_light(), _scale); + item_rect_changed(); +} + +real_t PointLight2D::get_texture_scale() const { + return _scale; +} + +void PointLight2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_texture", "texture"), &PointLight2D::set_texture); + ClassDB::bind_method(D_METHOD("get_texture"), &PointLight2D::get_texture); + + ClassDB::bind_method(D_METHOD("set_texture_offset", "texture_offset"), &PointLight2D::set_texture_offset); + ClassDB::bind_method(D_METHOD("get_texture_offset"), &PointLight2D::get_texture_offset); + + ClassDB::bind_method(D_METHOD("set_texture_scale", "texture_scale"), &PointLight2D::set_texture_scale); + ClassDB::bind_method(D_METHOD("get_texture_scale"), &PointLight2D::get_texture_scale); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_texture_offset", "get_texture_offset"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "texture_scale", PROPERTY_HINT_RANGE, "0.01,50,0.01"), "set_texture_scale", "get_texture_scale"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0,1024,1,or_greater"), "set_height", "get_height"); +} + +PointLight2D::PointLight2D() { + RS::get_singleton()->canvas_light_set_mode(_get_light(), RS::CANVAS_LIGHT_MODE_POINT); +} + +////////// + +void DirectionalLight2D::set_max_distance(real_t p_distance) { + max_distance = p_distance; + RS::get_singleton()->canvas_light_set_directional_distance(_get_light(), max_distance); +} + +real_t DirectionalLight2D::get_max_distance() const { + return max_distance; +} + +void DirectionalLight2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_max_distance", "pixels"), &DirectionalLight2D::set_max_distance); + ClassDB::bind_method(D_METHOD("get_max_distance"), &DirectionalLight2D::get_max_distance); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "0,16384.0,1.0,or_greater"), "set_max_distance", "get_max_distance"); +} + +DirectionalLight2D::DirectionalLight2D() { + RS::get_singleton()->canvas_light_set_mode(_get_light(), RS::CANVAS_LIGHT_MODE_DIRECTIONAL); + set_max_distance(max_distance); // Update RenderingServer. +} diff --git a/scene/2d/light_2d.h b/scene/2d/light_2d.h index 7134029441..d9ecd81f1c 100644 --- a/scene/2d/light_2d.h +++ b/scene/2d/light_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -34,17 +34,9 @@ #include "scene/2d/node_2d.h" class Light2D : public Node2D { - GDCLASS(Light2D, Node2D); public: - enum Mode { - MODE_ADD, - MODE_SUB, - MODE_MIX, - MODE_MASK, - }; - enum ShadowFilter { SHADOW_FILTER_NONE, SHADOW_FILTER_PCF5, @@ -52,72 +44,56 @@ public: SHADOW_FILTER_MAX }; + enum BlendMode { + BLEND_MODE_ADD, + BLEND_MODE_SUB, + BLEND_MODE_MIX, + }; + private: RID canvas_light; - bool enabled; - bool editor_only; - bool shadow; - Color color; - Color shadow_color; - float height; - float _scale; - float energy; - int z_min; - int z_max; - int layer_min; - int layer_max; - int item_mask; - int item_shadow_mask; - int shadow_buffer_size; - float shadow_smooth; - Mode mode; + bool enabled = true; + bool editor_only = false; + bool shadow = false; + Color color = Color(1, 1, 1); + Color shadow_color = Color(0, 0, 0, 0); + real_t height = 0.0; + real_t energy = 1.0; + int z_min = -1024; + int z_max = 1024; + int layer_min = 0; + int layer_max = 0; + int item_mask = 1; + int item_shadow_mask = 1; + real_t shadow_smooth = 0.0; Ref<Texture2D> texture; Vector2 texture_offset; - ShadowFilter shadow_filter; + ShadowFilter shadow_filter = SHADOW_FILTER_NONE; + BlendMode blend_mode = BLEND_MODE_ADD; void _update_light_visibility(); protected: + _FORCE_INLINE_ RID _get_light() const { return canvas_light; } void _notification(int p_what); static void _bind_methods(); + void _validate_property(PropertyInfo &property) const override; public: -#ifdef TOOLS_ENABLED - virtual Dictionary _edit_get_state() const; - virtual void _edit_set_state(const Dictionary &p_state); - - virtual void _edit_set_pivot(const Point2 &p_pivot); - virtual Point2 _edit_get_pivot() const; - virtual bool _edit_use_pivot() const; - virtual Rect2 _edit_get_rect() const; - virtual bool _edit_use_rect() const; -#endif - - virtual Rect2 get_anchorable_rect() const; - void set_enabled(bool p_enabled); bool is_enabled() const; void set_editor_only(bool p_editor_only); bool is_editor_only() const; - void set_texture(const Ref<Texture2D> &p_texture); - Ref<Texture2D> get_texture() const; - - void set_texture_offset(const Vector2 &p_offset); - Vector2 get_texture_offset() const; - void set_color(const Color &p_color); Color get_color() const; - void set_height(float p_height); - float get_height() const; - - void set_energy(float p_energy); - float get_energy() const; + void set_height(real_t p_height); + real_t get_height() const; - void set_texture_scale(float p_scale); - float get_texture_scale() const; + void set_energy(real_t p_energy); + real_t get_energy() const; void set_z_range_min(int p_min_z); int get_z_range_min() const; @@ -137,31 +113,80 @@ public: void set_item_shadow_cull_mask(int p_mask); int get_item_shadow_cull_mask() const; - void set_mode(Mode p_mode); - Mode get_mode() const; - void set_shadow_enabled(bool p_enabled); bool is_shadow_enabled() const; - void set_shadow_buffer_size(int p_size); - int get_shadow_buffer_size() const; - void set_shadow_filter(ShadowFilter p_filter); ShadowFilter get_shadow_filter() const; void set_shadow_color(const Color &p_shadow_color); Color get_shadow_color() const; - void set_shadow_smooth(float p_amount); - float get_shadow_smooth() const; + void set_shadow_smooth(real_t p_amount); + real_t get_shadow_smooth() const; - String get_configuration_warning() const; + void set_blend_mode(BlendMode p_mode); + BlendMode get_blend_mode() const; Light2D(); ~Light2D(); }; -VARIANT_ENUM_CAST(Light2D::Mode); VARIANT_ENUM_CAST(Light2D::ShadowFilter); +VARIANT_ENUM_CAST(Light2D::BlendMode); + +class PointLight2D : public Light2D { + GDCLASS(PointLight2D, Light2D); + +private: + real_t _scale = 1.0; + Ref<Texture2D> texture; + Vector2 texture_offset; + +protected: + static void _bind_methods(); + +public: +#ifdef TOOLS_ENABLED + virtual Dictionary _edit_get_state() const override; + virtual void _edit_set_state(const Dictionary &p_state) override; + + virtual void _edit_set_pivot(const Point2 &p_pivot) override; + virtual Point2 _edit_get_pivot() const override; + virtual bool _edit_use_pivot() const override; + virtual Rect2 _edit_get_rect() const override; + virtual bool _edit_use_rect() const override; +#endif + + virtual Rect2 get_anchorable_rect() const override; + + void set_texture(const Ref<Texture2D> &p_texture); + Ref<Texture2D> get_texture() const; + + void set_texture_offset(const Vector2 &p_offset); + Vector2 get_texture_offset() const; + + void set_texture_scale(real_t p_scale); + real_t get_texture_scale() const; + + TypedArray<String> get_configuration_warnings() const override; + + PointLight2D(); +}; + +class DirectionalLight2D : public Light2D { + GDCLASS(DirectionalLight2D, Light2D); + + real_t max_distance = 10000.0; + +protected: + static void _bind_methods(); + +public: + void set_max_distance(real_t p_distance); + real_t get_max_distance() const; + + DirectionalLight2D(); +}; #endif // LIGHT_2D_H diff --git a/scene/2d/light_occluder_2d.cpp b/scene/2d/light_occluder_2d.cpp index bd1a820aec..fdc28f81c2 100644 --- a/scene/2d/light_occluder_2d.cpp +++ b/scene/2d/light_occluder_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -29,24 +29,25 @@ /*************************************************************************/ #include "light_occluder_2d.h" +#include "core/math/geometry_2d.h" -#include "core/engine.h" +#include "core/config/engine.h" #define LINE_GRAB_WIDTH 8 #ifdef TOOLS_ENABLED Rect2 OccluderPolygon2D::_edit_get_rect() const { - if (rect_cache_dirty) { if (closed) { const Vector2 *r = polygon.ptr(); item_rect = Rect2(); for (int i = 0; i < polygon.size(); i++) { Vector2 pos = r[i]; - if (i == 0) + if (i == 0) { item_rect.position = pos; - else + } else { item_rect.expand_to(pos); + } } rect_cache_dirty = false; } else { @@ -67,16 +68,16 @@ Rect2 OccluderPolygon2D::_edit_get_rect() const { } bool OccluderPolygon2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { - if (closed) { - return Geometry::is_point_in_polygon(p_point, Variant(polygon)); + return Geometry2D::is_point_in_polygon(p_point, Variant(polygon)); } else { const real_t d = LINE_GRAB_WIDTH / 2 + p_tolerance; const Vector2 *points = polygon.ptr(); for (int i = 0; i < polygon.size() - 1; i++) { - Vector2 p = Geometry::get_closest_point_to_segment_2d(p_point, &points[i]); - if (p.distance_to(p_point) <= d) + Vector2 p = Geometry2D::get_closest_point_to_segment(p_point, &points[i]); + if (p.distance_to(p_point) <= d) { return true; + } } return false; @@ -85,7 +86,6 @@ bool OccluderPolygon2D::_edit_is_selected_on_click(const Point2 &p_point, double #endif void OccluderPolygon2D::set_polygon(const Vector<Vector2> &p_polygon) { - polygon = p_polygon; rect_cache_dirty = true; RS::get_singleton()->canvas_occluder_polygon_set_shape(occ_polygon, p_polygon, closed); @@ -93,43 +93,38 @@ void OccluderPolygon2D::set_polygon(const Vector<Vector2> &p_polygon) { } Vector<Vector2> OccluderPolygon2D::get_polygon() const { - return polygon; } void OccluderPolygon2D::set_closed(bool p_closed) { - - if (closed == p_closed) + if (closed == p_closed) { return; + } closed = p_closed; - if (polygon.size()) + if (polygon.size()) { RS::get_singleton()->canvas_occluder_polygon_set_shape(occ_polygon, polygon, closed); + } emit_changed(); } bool OccluderPolygon2D::is_closed() const { - return closed; } void OccluderPolygon2D::set_cull_mode(CullMode p_mode) { - cull = p_mode; RS::get_singleton()->canvas_occluder_polygon_set_cull_mode(occ_polygon, RS::CanvasOccluderPolygonCullMode(p_mode)); } OccluderPolygon2D::CullMode OccluderPolygon2D::get_cull_mode() const { - return cull; } RID OccluderPolygon2D::get_rid() const { - return occ_polygon; } void OccluderPolygon2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_closed", "closed"), &OccluderPolygon2D::set_closed); ClassDB::bind_method(D_METHOD("is_closed"), &OccluderPolygon2D::is_closed); @@ -149,48 +144,35 @@ void OccluderPolygon2D::_bind_methods() { } OccluderPolygon2D::OccluderPolygon2D() { - occ_polygon = RS::get_singleton()->canvas_occluder_polygon_create(); - closed = true; - cull = CULL_DISABLED; - rect_cache_dirty = true; } OccluderPolygon2D::~OccluderPolygon2D() { - RS::get_singleton()->free(occ_polygon); } void LightOccluder2D::_poly_changed() { - #ifdef DEBUG_ENABLED update(); #endif } void LightOccluder2D::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_CANVAS) { - RS::get_singleton()->canvas_light_occluder_attach_to_canvas(occluder, get_canvas()); RS::get_singleton()->canvas_light_occluder_set_transform(occluder, get_global_transform()); RS::get_singleton()->canvas_light_occluder_set_enabled(occluder, is_visible_in_tree()); } if (p_what == NOTIFICATION_TRANSFORM_CHANGED) { - RS::get_singleton()->canvas_light_occluder_set_transform(occluder, get_global_transform()); } if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - RS::get_singleton()->canvas_light_occluder_set_enabled(occluder, is_visible_in_tree()); } if (p_what == NOTIFICATION_DRAW) { - if (Engine::get_singleton()->is_editor_hint()) { - if (occluder_polygon.is_valid()) { - Vector<Vector2> poly = occluder_polygon->get_polygon(); if (poly.size()) { @@ -199,11 +181,9 @@ void LightOccluder2D::_notification(int p_what) { color.push_back(Color(0, 0, 0, 0.6)); draw_polygon(Variant(poly), color); } else { - int ps = poly.size(); const Vector2 *r = poly.ptr(); for (int i = 0; i < ps - 1; i++) { - draw_line(r[i], r[i + 1], Color(0, 0, 0, 0.6), 3); } } @@ -213,92 +193,100 @@ void LightOccluder2D::_notification(int p_what) { } if (p_what == NOTIFICATION_EXIT_CANVAS) { - RS::get_singleton()->canvas_light_occluder_attach_to_canvas(occluder, RID()); } } #ifdef TOOLS_ENABLED Rect2 LightOccluder2D::_edit_get_rect() const { - return occluder_polygon.is_valid() ? occluder_polygon->_edit_get_rect() : Rect2(); } bool LightOccluder2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { - return occluder_polygon.is_valid() ? occluder_polygon->_edit_is_selected_on_click(p_point, p_tolerance) : false; } #endif void LightOccluder2D::set_occluder_polygon(const Ref<OccluderPolygon2D> &p_polygon) { - #ifdef DEBUG_ENABLED - if (occluder_polygon.is_valid()) + if (occluder_polygon.is_valid()) { occluder_polygon->disconnect("changed", callable_mp(this, &LightOccluder2D::_poly_changed)); + } #endif occluder_polygon = p_polygon; - if (occluder_polygon.is_valid()) + if (occluder_polygon.is_valid()) { RS::get_singleton()->canvas_light_occluder_set_polygon(occluder, occluder_polygon->get_rid()); - else + } else { RS::get_singleton()->canvas_light_occluder_set_polygon(occluder, RID()); + } #ifdef DEBUG_ENABLED - if (occluder_polygon.is_valid()) + if (occluder_polygon.is_valid()) { occluder_polygon->connect("changed", callable_mp(this, &LightOccluder2D::_poly_changed)); + } update(); #endif } Ref<OccluderPolygon2D> LightOccluder2D::get_occluder_polygon() const { - return occluder_polygon; } void LightOccluder2D::set_occluder_light_mask(int p_mask) { - mask = p_mask; - RS::get_singleton()->canvas_light_occluder_set_light_mask(occluder, mask); + RS::get_singleton()->canvas_light_occluder_set_light_mask(occluder, p_mask); } int LightOccluder2D::get_occluder_light_mask() const { - return mask; } -String LightOccluder2D::get_configuration_warning() const { +TypedArray<String> LightOccluder2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); if (!occluder_polygon.is_valid()) { - return TTR("An occluder polygon must be set (or drawn) for this occluder to take effect."); + warnings.push_back(TTR("An occluder polygon must be set (or drawn) for this occluder to take effect.")); } if (occluder_polygon.is_valid() && occluder_polygon->get_polygon().size() == 0) { - return TTR("The occluder polygon for this occluder is empty. Please draw a polygon."); + warnings.push_back(TTR("The occluder polygon for this occluder is empty. Please draw a polygon.")); } - return String(); + return warnings; } -void LightOccluder2D::_bind_methods() { +void LightOccluder2D::set_as_sdf_collision(bool p_enable) { + sdf_collision = p_enable; + RS::get_singleton()->canvas_light_occluder_set_as_sdf_collision(occluder, sdf_collision); +} +bool LightOccluder2D::is_set_as_sdf_collision() const { + return sdf_collision; +} +void LightOccluder2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_occluder_polygon", "polygon"), &LightOccluder2D::set_occluder_polygon); ClassDB::bind_method(D_METHOD("get_occluder_polygon"), &LightOccluder2D::get_occluder_polygon); ClassDB::bind_method(D_METHOD("set_occluder_light_mask", "mask"), &LightOccluder2D::set_occluder_light_mask); ClassDB::bind_method(D_METHOD("get_occluder_light_mask"), &LightOccluder2D::get_occluder_light_mask); + ClassDB::bind_method(D_METHOD("set_as_sdf_collision", "enable"), &LightOccluder2D::set_as_sdf_collision); + ClassDB::bind_method(D_METHOD("is_set_as_sdf_collision"), &LightOccluder2D::is_set_as_sdf_collision); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "occluder", PROPERTY_HINT_RESOURCE_TYPE, "OccluderPolygon2D"), "set_occluder_polygon", "get_occluder_polygon"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "light_mask", PROPERTY_HINT_LAYERS_2D_RENDER), "set_occluder_light_mask", "get_occluder_light_mask"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sdf_collision"), "set_as_sdf_collision", "is_set_as_sdf_collision"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "occluder_light_mask", PROPERTY_HINT_LAYERS_2D_RENDER), "set_occluder_light_mask", "get_occluder_light_mask"); } LightOccluder2D::LightOccluder2D() { - occluder = RS::get_singleton()->canvas_light_occluder_create(); mask = 1; + set_notify_transform(true); + set_as_sdf_collision(true); } LightOccluder2D::~LightOccluder2D() { - RS::get_singleton()->free(occluder); } diff --git a/scene/2d/light_occluder_2d.h b/scene/2d/light_occluder_2d.h index 83702f2875..b4a48d1062 100644 --- a/scene/2d/light_occluder_2d.h +++ b/scene/2d/light_occluder_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -34,7 +34,6 @@ #include "scene/2d/node_2d.h" class OccluderPolygon2D : public Resource { - GDCLASS(OccluderPolygon2D, Resource); public: @@ -47,11 +46,11 @@ public: private: RID occ_polygon; Vector<Vector2> polygon; - bool closed; - CullMode cull; + bool closed = true; + CullMode cull = CULL_DISABLED; mutable Rect2 item_rect; - mutable bool rect_cache_dirty; + mutable bool rect_cache_dirty = true; protected: static void _bind_methods(); @@ -71,7 +70,7 @@ public: void set_cull_mode(CullMode p_mode); CullMode get_cull_mode() const; - virtual RID get_rid() const; + virtual RID get_rid() const override; OccluderPolygon2D(); ~OccluderPolygon2D(); }; @@ -85,7 +84,7 @@ class LightOccluder2D : public Node2D { bool enabled; int mask; Ref<OccluderPolygon2D> occluder_polygon; - + bool sdf_collision; void _poly_changed(); protected: @@ -94,8 +93,8 @@ protected: public: #ifdef TOOLS_ENABLED - virtual Rect2 _edit_get_rect() const; - virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const; + virtual Rect2 _edit_get_rect() const override; + virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override; #endif void set_occluder_polygon(const Ref<OccluderPolygon2D> &p_polygon); @@ -104,7 +103,10 @@ public: void set_occluder_light_mask(int p_mask); int get_occluder_light_mask() const; - String get_configuration_warning() const; + void set_as_sdf_collision(bool p_enable); + bool is_set_as_sdf_collision() const; + + TypedArray<String> get_configuration_warnings() const override; LightOccluder2D(); ~LightOccluder2D(); diff --git a/scene/2d/line_2d.cpp b/scene/2d/line_2d.cpp index c45eab70df..37eb45c21d 100644 --- a/scene/2d/line_2d.cpp +++ b/scene/2d/line_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,6 +31,7 @@ #include "line_2d.h" #include "core/core_string_names.h" +#include "core/math/geometry_2d.h" #include "line_builder.h" // Needed so we can bind functions @@ -39,22 +40,13 @@ VARIANT_ENUM_CAST(Line2D::LineCapMode) VARIANT_ENUM_CAST(Line2D::LineTextureMode) Line2D::Line2D() { - _joint_mode = LINE_JOINT_SHARP; - _begin_cap_mode = LINE_CAP_NONE; - _end_cap_mode = LINE_CAP_NONE; - _width = 10; - _default_color = Color(0.4, 0.5, 1); - _texture_mode = LINE_TEXTURE_NONE; - _sharp_limit = 2.f; - _round_precision = 8; - _antialiased = false; } #ifdef TOOLS_ENABLED Rect2 Line2D::_edit_get_rect() const { - - if (_points.size() == 0) + if (_points.size() == 0) { return Rect2(0, 0, 0, 0); + } Vector2 d = Vector2(_width, _width); Rect2 aabb = Rect2(_points[0] - d, 2 * d); for (int i = 1; i < _points.size(); i++) { @@ -69,13 +61,13 @@ bool Line2D::_edit_use_rect() const { } bool Line2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { - const real_t d = _width / 2 + p_tolerance; const Vector2 *points = _points.ptr(); for (int i = 0; i < _points.size() - 1; i++) { - Vector2 p = Geometry::get_closest_point_to_segment_2d(p_point, &points[i]); - if (p.distance_to(p_point) <= d) + Vector2 p = Geometry2D::get_closest_point_to_segment(p_point, &points[i]); + if (p.distance_to(p_point) <= d) { return true; + } } return false; @@ -88,8 +80,9 @@ void Line2D::set_points(const Vector<Vector2> &p_points) { } void Line2D::set_width(float p_width) { - if (p_width < 0.0) + if (p_width < 0.0) { p_width = 0.0; + } _width = p_width; update(); } @@ -123,6 +116,7 @@ Vector<Vector2> Line2D::get_points() const { } void Line2D::set_point_position(int i, Vector2 p_pos) { + ERR_FAIL_INDEX(i, _points.size()); _points.set(i, p_pos); update(); } @@ -168,7 +162,6 @@ Color Line2D::get_default_color() const { } void Line2D::set_gradient(const Ref<Gradient> &p_gradient) { - // Cleanup previous connection if any if (_gradient.is_valid()) { _gradient->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Line2D::_gradient_changed)); @@ -176,7 +169,7 @@ void Line2D::set_gradient(const Ref<Gradient> &p_gradient) { _gradient = p_gradient; - // Connect to the gradient so the line will update when the ColorRamp is changed + // Connect to the gradient so the line will update when the Gradient is changed if (_gradient.is_valid()) { _gradient->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Line2D::_gradient_changed)); } @@ -242,8 +235,9 @@ void Line2D::_notification(int p_what) { } void Line2D::set_sharp_limit(float p_limit) { - if (p_limit < 0.f) + if (p_limit < 0.f) { p_limit = 0.f; + } _sharp_limit = p_limit; update(); } @@ -253,8 +247,9 @@ float Line2D::get_sharp_limit() const { } void Line2D::set_round_precision(int p_precision) { - if (p_precision < 1) + if (p_precision < 1) { p_precision = 1; + } _round_precision = p_precision; update(); } @@ -273,8 +268,9 @@ bool Line2D::get_antialiased() const { } void Line2D::_draw() { - if (_points.size() <= 1 || _width == 0.f) + if (_points.size() <= 1 || _width == 0.f) { return; + } // TODO Is this really needed? // Copy points for faster access @@ -350,7 +346,6 @@ void Line2D::_curve_changed() { // static void Line2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_points", "points"), &Line2D::set_points); ClassDB::bind_method(D_METHOD("get_points"), &Line2D::get_points); diff --git a/scene/2d/line_2d.h b/scene/2d/line_2d.h index 51706befdb..5e7eb4bac9 100644 --- a/scene/2d/line_2d.h +++ b/scene/2d/line_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -34,7 +34,6 @@ #include "node_2d.h" class Line2D : public Node2D { - GDCLASS(Line2D, Node2D); public: @@ -57,9 +56,9 @@ public: }; #ifdef TOOLS_ENABLED - virtual Rect2 _edit_get_rect() const; - virtual bool _edit_use_rect() const; - virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const; + virtual Rect2 _edit_get_rect() const override; + virtual bool _edit_use_rect() const override; + virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override; #endif Line2D(); @@ -125,18 +124,18 @@ private: private: Vector<Vector2> _points; - LineJointMode _joint_mode; - LineCapMode _begin_cap_mode; - LineCapMode _end_cap_mode; - float _width; + LineJointMode _joint_mode = LINE_JOINT_SHARP; + LineCapMode _begin_cap_mode = LINE_CAP_NONE; + LineCapMode _end_cap_mode = LINE_CAP_NONE; + float _width = 10.0; Ref<Curve> _curve; - Color _default_color; + Color _default_color = Color(1, 1, 1); Ref<Gradient> _gradient; Ref<Texture2D> _texture; - LineTextureMode _texture_mode; - float _sharp_limit; - int _round_precision; - bool _antialiased; + LineTextureMode _texture_mode = LINE_TEXTURE_NONE; + float _sharp_limit = 2.f; + int _round_precision = 8; + bool _antialiased = false; }; #endif // LINE2D_H diff --git a/scene/2d/line_builder.cpp b/scene/2d/line_builder.cpp index 6b06f2227a..a8a2639ccf 100644 --- a/scene/2d/line_builder.cpp +++ b/scene/2d/line_builder.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -53,22 +53,15 @@ static SegmentIntersectionResult segment_intersection( float ub = (ab.x * (a.y - c.y) - ab.y * (a.x - c.x)) / div; *out_intersection = a + ua * ab; if (ua >= 0.f && ua <= 1.f && - ub >= 0.f && ub <= 1.f) + ub >= 0.f && ub <= 1.f) { return SEGMENT_INTERSECT; + } return SEGMENT_NO_INTERSECT; } 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) { @@ -93,20 +86,6 @@ static inline Vector2 interpolate(const Rect2 &r, const Vector2 &v) { //---------------------------------------------------------------------------- LineBuilder::LineBuilder() { - joint_mode = Line2D::LINE_JOINT_SHARP; - width = 10; - curve = nullptr; - default_color = Color(0.4, 0.5, 1); - gradient = nullptr; - sharp_limit = 2.f; - round_precision = 8; - begin_cap_mode = Line2D::LINE_CAP_NONE; - end_cap_mode = Line2D::LINE_CAP_NONE; - tile_aspect = 1.f; - - _interpolate_color = false; - _last_index[0] = 0; - _last_index[1] = 0; } void LineBuilder::clear_output() { @@ -117,7 +96,6 @@ void LineBuilder::clear_output() { } void LineBuilder::build() { - // Need at least 2 points to draw a line if (points.size() < 2) { clear_output(); @@ -158,28 +136,32 @@ void LineBuilder::build() { //Adjust totalDistance. // The line's outer length will be a little higher due to begin and end caps if (begin_cap_mode == Line2D::LINE_CAP_BOX || begin_cap_mode == Line2D::LINE_CAP_ROUND) { - if (retrieve_curve) + if (retrieve_curve) { total_distance += width * curve->interpolate_baked(0.f) * 0.5f; - else + } else { total_distance += width * 0.5f; + } } if (end_cap_mode == Line2D::LINE_CAP_BOX || end_cap_mode == Line2D::LINE_CAP_ROUND) { - if (retrieve_curve) + if (retrieve_curve) { total_distance += width * curve->interpolate_baked(1.f) * 0.5f; - else + } else { total_distance += width * 0.5f; + } } } - if (_interpolate_color) + if (_interpolate_color) { color0 = gradient->get_color(0); - else + } else { colors.push_back(default_color); + } float uvx0 = 0.f; float uvx1 = 0.f; - if (retrieve_curve) + if (retrieve_curve) { width_factor = curve->interpolate_baked(0.f); + } pos_up0 += u0 * hw * width_factor; pos_down0 -= u0 * hw * width_factor; @@ -220,7 +202,6 @@ void LineBuilder::build() { // For each additional segment for (int i = 1; i < len - 1; ++i) { - pos1 = points[i]; Vector2 pos2 = points[i + 1]; @@ -271,10 +252,10 @@ void LineBuilder::build() { pos1 + inner_normal1, pos2 + inner_normal1, &corner_pos_in); - if (intersection_result == SEGMENT_INTERSECT) + if (intersection_result == SEGMENT_INTERSECT) { // Inner parts of the segments intersect corner_pos_out = 2.f * pos1 - corner_pos_in; - else { + } else { // No intersection, segments are either parallel or too sharp corner_pos_in = pos1 + inner_normal0; corner_pos_out = pos1 - inner_normal0; @@ -359,7 +340,6 @@ void LineBuilder::build() { // Add joint geometry if (current_joint_mode != Line2D::LINE_JOINT_SHARP) { - /* ________________ cbegin * / \ * / \ @@ -386,10 +366,11 @@ void LineBuilder::build() { strip_add_arc(pos1, vbegin.angle_to(vend), orientation); } - if (intersection_result != SEGMENT_INTERSECT) - // In this case the joint is too corrputed to be re-used, + if (intersection_result != SEGMENT_INTERSECT) { + // In this case the joint is too corrupted to be re-used, // start again the strip with fallback points strip_begin(pos_up0, pos_down0, color1, uvx1); + } } } // Last (or only) segment @@ -456,39 +437,6 @@ void LineBuilder::strip_begin(Vector2 up, Vector2 down, Color color, float uvx) _last_index[DOWN] = vi + 1; } -void LineBuilder::strip_new_quad(Vector2 up, Vector2 down, Color color, float uvx) { - int vi = vertices.size(); - - vertices.push_back(vertices[_last_index[UP]]); - vertices.push_back(vertices[_last_index[DOWN]]); - vertices.push_back(up); - vertices.push_back(down); - - if (_interpolate_color) { - colors.push_back(color); - colors.push_back(color); - colors.push_back(color); - colors.push_back(color); - } - - if (texture_mode != Line2D::LINE_TEXTURE_NONE) { - uvs.push_back(uvs[_last_index[UP]]); - uvs.push_back(uvs[_last_index[DOWN]]); - uvs.push_back(Vector2(uvx, UP)); - uvs.push_back(Vector2(uvx, DOWN)); - } - - indices.push_back(vi); - indices.push_back(vi + 3); - indices.push_back(vi + 1); - indices.push_back(vi); - indices.push_back(vi + 2); - indices.push_back(vi + 3); - - _last_index[UP] = vi + 2; - _last_index[DOWN] = vi + 3; -} - void LineBuilder::strip_add_quad(Vector2 up, Vector2 down, Color color, float uvx) { int vi = vertices.size(); @@ -529,7 +477,7 @@ void LineBuilder::strip_add_tri(Vector2 up, Orientation orientation) { if (texture_mode != Line2D::LINE_TEXTURE_NONE) { // UVs are just one slice of the texture all along - // (otherwise we can't share the bottom vertice) + // (otherwise we can't share the bottom vertex) uvs.push_back(uvs[_last_index[opposite_orientation]]); } @@ -541,7 +489,6 @@ void LineBuilder::strip_add_tri(Vector2 up, Orientation orientation) { } void LineBuilder::strip_add_arc(Vector2 center, float angle_delta, Orientation orientation) { - // Take the two last vertices and extrude an arc made of triangles // that all share one of the initial vertices @@ -551,8 +498,9 @@ void LineBuilder::strip_add_arc(Vector2 center, float angle_delta, Orientation o float angle_step = Math_PI / static_cast<float>(round_precision); float steps = Math::abs(angle_delta) / angle_step; - if (angle_delta < 0.f) + if (angle_delta < 0.f) { angle_step = -angle_step; + } float t = Vector2(1, 0).angle_to(vbegin); float end_angle = t + angle_delta; @@ -564,13 +512,12 @@ void LineBuilder::strip_add_arc(Vector2 center, float angle_delta, Orientation o strip_add_tri(rpos, orientation); } - // Last arc vertice + // Last arc vertex rpos = center + Vector2(Math::cos(end_angle), Math::sin(end_angle)) * radius; strip_add_tri(rpos, orientation); } void LineBuilder::new_arc(Vector2 center, Vector2 vbegin, float angle_delta, Color color, Rect2 uv_rect) { - // Make a standalone arc that doesn't use existing vertices, // with undistorted UVs from within a square section @@ -578,22 +525,25 @@ void LineBuilder::new_arc(Vector2 center, Vector2 vbegin, float angle_delta, Col float angle_step = Math_PI / static_cast<float>(round_precision); float steps = Math::abs(angle_delta) / angle_step; - if (angle_delta < 0.f) + if (angle_delta < 0.f) { angle_step = -angle_step; + } float t = Vector2(1, 0).angle_to(vbegin); float end_angle = t + angle_delta; Vector2 rpos(0, 0); - float tt_begin = -Math_PI / 2.f; + float tt_begin = -Math_PI / 2.0f; float tt = tt_begin; // Center vertice int vi = vertices.size(); vertices.push_back(center); - if (_interpolate_color) + if (_interpolate_color) { colors.push_back(color); - if (texture_mode != Line2D::LINE_TEXTURE_NONE) + } + if (texture_mode != Line2D::LINE_TEXTURE_NONE) { uvs.push_back(interpolate(uv_rect, Vector2(0.5f, 0.5f))); + } // Arc vertices for (int ti = 0; ti < steps; ++ti, t += angle_step) { @@ -601,8 +551,9 @@ void LineBuilder::new_arc(Vector2 center, Vector2 vbegin, float angle_delta, Col rpos = center + sc * radius; vertices.push_back(rpos); - if (_interpolate_color) + if (_interpolate_color) { colors.push_back(color); + } if (texture_mode != Line2D::LINE_TEXTURE_NONE) { Vector2 tsc = Vector2(Math::cos(tt), Math::sin(tt)); uvs.push_back(interpolate(uv_rect, 0.5f * (tsc + Vector2(1.f, 1.f)))); @@ -610,12 +561,13 @@ void LineBuilder::new_arc(Vector2 center, Vector2 vbegin, float angle_delta, Col } } - // Last arc vertice + // Last arc vertex Vector2 sc = Vector2(Math::cos(end_angle), Math::sin(end_angle)); rpos = center + sc * radius; vertices.push_back(rpos); - if (_interpolate_color) + if (_interpolate_color) { colors.push_back(color); + } if (texture_mode != Line2D::LINE_TEXTURE_NONE) { tt = tt_begin + angle_delta; Vector2 tsc = Vector2(Math::cos(tt), Math::sin(tt)); diff --git a/scene/2d/line_builder.h b/scene/2d/line_builder.h index 9e3dbbd6c1..16c88d00e9 100644 --- a/scene/2d/line_builder.h +++ b/scene/2d/line_builder.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,27 +31,24 @@ #ifndef LINE_BUILDER_H #define LINE_BUILDER_H -#include "core/color.h" -#include "core/math/vector2.h" #include "line_2d.h" -#include "scene/resources/gradient.h" class LineBuilder { public: // TODO Move in a struct and reference it // Input Vector<Vector2> points; - Line2D::LineJointMode joint_mode; - Line2D::LineCapMode begin_cap_mode; - Line2D::LineCapMode end_cap_mode; - float width; - Curve *curve; - Color default_color; - Gradient *gradient; - Line2D::LineTextureMode texture_mode; - float sharp_limit; - int round_precision; - float tile_aspect; // w/h + Line2D::LineJointMode joint_mode = Line2D::LINE_JOINT_SHARP; + Line2D::LineCapMode begin_cap_mode = Line2D::LINE_CAP_NONE; + Line2D::LineCapMode end_cap_mode = Line2D::LINE_CAP_NONE; + float width = 10.0; + Curve *curve = nullptr; + Color default_color = Color(0.4, 0.5, 1); + Gradient *gradient = nullptr; + Line2D::LineTextureMode texture_mode = Line2D::LineTextureMode::LINE_TEXTURE_NONE; + float sharp_limit = 2.f; + int round_precision = 8; + float tile_aspect = 1.f; // w/h // TODO offset_joints option (offers alternative implementation of round joints) // TODO Move in a struct and reference it @@ -82,8 +79,8 @@ private: void new_arc(Vector2 center, Vector2 vbegin, float angle_delta, Color color, Rect2 uv_rect); private: - bool _interpolate_color; - int _last_index[2]; // Index of last up and down vertices of the strip + bool _interpolate_color = false; + int _last_index[2] = {}; // Index of last up and down vertices of the strip }; #endif // LINE_BUILDER_H 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/y_sort.cpp b/scene/2d/listener_2d.h index 15d97eeaa0..0289a8087d 100644 --- a/scene/2d/y_sort.cpp +++ b/scene/2d/listener_2d.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* y_sort.cpp */ +/* listener_2d.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,30 +28,34 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "y_sort.h" +#ifndef LISTENER_2D_H +#define LISTENER_2D_H -void YSort::set_sort_enabled(bool p_enabled) { +#include "scene/2d/node_2d.h" +#include "scene/main/window.h" - sort_enabled = p_enabled; - RS::get_singleton()->canvas_item_set_sort_children_by_y(get_canvas_item(), sort_enabled); -} +class Listener2D : public Node2D { + GDCLASS(Listener2D, Node2D); -bool YSort::is_sort_enabled() const { +private: + bool current = false; - return sort_enabled; -} + friend class Viewport; -void YSort::_bind_methods() { +protected: + void _update_listener(); - ClassDB::bind_method(D_METHOD("set_sort_enabled", "enabled"), &YSort::set_sort_enabled); - ClassDB::bind_method(D_METHOD("is_sort_enabled"), &YSort::is_sort_enabled); + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + void _notification(int p_what); - ADD_GROUP("Sort", "sort_"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sort_enabled"), "set_sort_enabled", "is_sort_enabled"); -} + static void _bind_methods(); -YSort::YSort() { +public: + void make_current(); + void clear_current(); + bool is_current() const; +}; - sort_enabled = true; - RS::get_singleton()->canvas_item_set_sort_children_by_y(get_canvas_item(), true); -} +#endif diff --git a/scene/2d/mesh_instance_2d.cpp b/scene/2d/mesh_instance_2d.cpp index 5e258be700..15008390b7 100644 --- a/scene/2d/mesh_instance_2d.cpp +++ b/scene/2d/mesh_instance_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -29,18 +29,17 @@ /*************************************************************************/ #include "mesh_instance_2d.h" +#include "scene/scene_string_names.h" void MeshInstance2D::_notification(int p_what) { - if (p_what == NOTIFICATION_DRAW) { if (mesh.is_valid()) { - draw_mesh(mesh, texture, normal_map); + draw_mesh(mesh, texture); } } } void MeshInstance2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &MeshInstance2D::set_mesh); ClassDB::bind_method(D_METHOD("get_mesh"), &MeshInstance2D::get_mesh); @@ -58,45 +57,38 @@ void MeshInstance2D::_bind_methods() { } void MeshInstance2D::set_mesh(const Ref<Mesh> &p_mesh) { - mesh = p_mesh; update(); } Ref<Mesh> MeshInstance2D::get_mesh() const { - return mesh; } void MeshInstance2D::set_texture(const Ref<Texture2D> &p_texture) { - - if (p_texture == texture) + if (p_texture == texture) { return; + } texture = p_texture; update(); - emit_signal("texture_changed"); - _change_notify("texture"); + emit_signal(SceneStringNames::get_singleton()->texture_changed); } void MeshInstance2D::set_normal_map(const Ref<Texture2D> &p_texture) { - normal_map = p_texture; update(); } Ref<Texture2D> MeshInstance2D::get_normal_map() const { - return normal_map; } Ref<Texture2D> MeshInstance2D::get_texture() const { - return texture; } #ifdef TOOLS_ENABLED Rect2 MeshInstance2D::_edit_get_rect() const { - if (mesh.is_valid()) { AABB aabb = mesh->get_aabb(); return Rect2(aabb.position.x, aabb.position.y, aabb.size.x, aabb.size.y); diff --git a/scene/2d/mesh_instance_2d.h b/scene/2d/mesh_instance_2d.h index 3356f44e91..adfda4cf7f 100644 --- a/scene/2d/mesh_instance_2d.h +++ b/scene/2d/mesh_instance_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -47,7 +47,7 @@ protected: public: #ifdef TOOLS_ENABLED - virtual Rect2 _edit_get_rect() const; + virtual Rect2 _edit_get_rect() const override; #endif void set_mesh(const Ref<Mesh> &p_mesh); diff --git a/scene/2d/multimesh_instance_2d.cpp b/scene/2d/multimesh_instance_2d.cpp index 6620027020..1bff2f337d 100644 --- a/scene/2d/multimesh_instance_2d.cpp +++ b/scene/2d/multimesh_instance_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -29,18 +29,17 @@ /*************************************************************************/ #include "multimesh_instance_2d.h" +#include "scene/scene_string_names.h" void MultiMeshInstance2D::_notification(int p_what) { - if (p_what == NOTIFICATION_DRAW) { if (multimesh.is_valid()) { - draw_multimesh(multimesh, texture, normal_map); + draw_multimesh(multimesh, texture); } } } void MultiMeshInstance2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_multimesh", "multimesh"), &MultiMeshInstance2D::set_multimesh); ClassDB::bind_method(D_METHOD("get_multimesh"), &MultiMeshInstance2D::get_multimesh); @@ -58,45 +57,38 @@ void MultiMeshInstance2D::_bind_methods() { } void MultiMeshInstance2D::set_multimesh(const Ref<MultiMesh> &p_multimesh) { - multimesh = p_multimesh; update(); } Ref<MultiMesh> MultiMeshInstance2D::get_multimesh() const { - return multimesh; } void MultiMeshInstance2D::set_texture(const Ref<Texture2D> &p_texture) { - - if (p_texture == texture) + if (p_texture == texture) { return; + } texture = p_texture; update(); - emit_signal("texture_changed"); - _change_notify("texture"); + emit_signal(SceneStringNames::get_singleton()->texture_changed); } Ref<Texture2D> MultiMeshInstance2D::get_texture() const { - return texture; } void MultiMeshInstance2D::set_normal_map(const Ref<Texture2D> &p_texture) { - normal_map = p_texture; update(); } Ref<Texture2D> MultiMeshInstance2D::get_normal_map() const { - return normal_map; } #ifdef TOOLS_ENABLED Rect2 MultiMeshInstance2D::_edit_get_rect() const { - if (multimesh.is_valid()) { AABB aabb = multimesh->get_aabb(); return Rect2(aabb.position.x, aabb.position.y, aabb.size.x, aabb.size.y); diff --git a/scene/2d/multimesh_instance_2d.h b/scene/2d/multimesh_instance_2d.h index a843606ebf..213cbd19b0 100644 --- a/scene/2d/multimesh_instance_2d.h +++ b/scene/2d/multimesh_instance_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -48,7 +48,7 @@ protected: public: #ifdef TOOLS_ENABLED - virtual Rect2 _edit_get_rect() const; + virtual Rect2 _edit_get_rect() const override; #endif void set_multimesh(const Ref<MultiMesh> &p_multimesh); diff --git a/scene/2d/navigation_2d.cpp b/scene/2d/navigation_2d.cpp deleted file mode 100644 index ae9fc0f32c..0000000000 --- a/scene/2d/navigation_2d.cpp +++ /dev/null @@ -1,94 +0,0 @@ -/*************************************************************************/ -/* navigation_2d.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 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 "navigation_2d.h" -#include "servers/navigation_server_2d.h" - -void Navigation2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_rid"), &Navigation2D::get_rid); - - ClassDB::bind_method(D_METHOD("get_simple_path", "start", "end", "optimize"), &Navigation2D::get_simple_path, DEFVAL(true)); - ClassDB::bind_method(D_METHOD("get_closest_point", "to_point"), &Navigation2D::get_closest_point); - ClassDB::bind_method(D_METHOD("get_closest_point_owner", "to_point"), &Navigation2D::get_closest_point_owner); - - ClassDB::bind_method(D_METHOD("set_cell_size", "cell_size"), &Navigation2D::set_cell_size); - ClassDB::bind_method(D_METHOD("get_cell_size"), &Navigation2D::get_cell_size); - - ClassDB::bind_method(D_METHOD("set_edge_connection_margin", "margin"), &Navigation2D::set_edge_connection_margin); - ClassDB::bind_method(D_METHOD("get_edge_connection_margin"), &Navigation2D::get_edge_connection_margin); - - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "cell_size"), "set_cell_size", "get_cell_size"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "edge_connection_margin"), "set_edge_connection_margin", "get_edge_connection_margin"); -} - -void Navigation2D::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_READY: { - NavigationServer2D::get_singleton()->map_set_active(map, true); - } break; - case NOTIFICATION_EXIT_TREE: { - - NavigationServer2D::get_singleton()->map_set_active(map, false); - } break; - } -} - -void Navigation2D::set_cell_size(float p_cell_size) { - cell_size = p_cell_size; - NavigationServer2D::get_singleton()->map_set_cell_size(map, cell_size); -} - -void Navigation2D::set_edge_connection_margin(float p_edge_connection_margin) { - edge_connection_margin = p_edge_connection_margin; - NavigationServer2D::get_singleton()->map_set_edge_connection_margin(map, edge_connection_margin); -} - -Vector<Vector2> Navigation2D::get_simple_path(const Vector2 &p_start, const Vector2 &p_end, bool p_optimize) const { - return NavigationServer2D::get_singleton()->map_get_path(map, p_start, p_end, p_optimize); -} - -Vector2 Navigation2D::get_closest_point(const Vector2 &p_point) const { - return NavigationServer2D::get_singleton()->map_get_closest_point(map, p_point); -} - -RID Navigation2D::get_closest_point_owner(const Vector2 &p_point) const { - return NavigationServer2D::get_singleton()->map_get_closest_point_owner(map, p_point); -} - -Navigation2D::Navigation2D() { - - map = NavigationServer2D::get_singleton()->map_create(); - set_cell_size(10); // Ten pixels - set_edge_connection_margin(100); -} - -Navigation2D::~Navigation2D() { - NavigationServer2D::get_singleton()->free(map); -} diff --git a/scene/2d/navigation_agent_2d.cpp b/scene/2d/navigation_agent_2d.cpp index 32da46e8a8..2f00978123 100644 --- a/scene/2d/navigation_agent_2d.cpp +++ b/scene/2d/navigation_agent_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,11 +30,11 @@ #include "navigation_agent_2d.h" -#include "core/engine.h" -#include "scene/2d/navigation_2d.h" +#include "core/math/geometry_2d.h" #include "servers/navigation_server_2d.h" void NavigationAgent2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_rid"), &NavigationAgent2D::get_rid); ClassDB::bind_method(D_METHOD("set_target_desired_distance", "desired_distance"), &NavigationAgent2D::set_target_desired_distance); ClassDB::bind_method(D_METHOD("get_target_desired_distance"), &NavigationAgent2D::get_target_desired_distance); @@ -42,9 +42,6 @@ void NavigationAgent2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_radius", "radius"), &NavigationAgent2D::set_radius); ClassDB::bind_method(D_METHOD("get_radius"), &NavigationAgent2D::get_radius); - ClassDB::bind_method(D_METHOD("set_navigation", "navigation"), &NavigationAgent2D::set_navigation_node); - ClassDB::bind_method(D_METHOD("get_navigation"), &NavigationAgent2D::get_navigation_node); - ClassDB::bind_method(D_METHOD("set_neighbor_dist", "neighbor_dist"), &NavigationAgent2D::set_neighbor_dist); ClassDB::bind_method(D_METHOD("get_neighbor_dist"), &NavigationAgent2D::get_neighbor_dist); @@ -91,57 +88,28 @@ void NavigationAgent2D::_bind_methods() { void NavigationAgent2D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_READY: { - agent_parent = Object::cast_to<Node2D>(get_parent()); - - NavigationServer2D::get_singleton()->agent_set_callback(agent, this, "_avoidance_done"); - - // Search the navigation node and set it - { - Navigation2D *nav = nullptr; - Node *p = get_parent(); - while (p != nullptr) { - nav = Object::cast_to<Navigation2D>(p); - if (nav != nullptr) - p = nullptr; - else - p = p->get_parent(); - } - - set_navigation(nav); + if (agent_parent != nullptr) { + // place agent on navigation map first or else the RVO agent callback creation fails silently later + NavigationServer2D::get_singleton()->agent_set_map(get_rid(), agent_parent->get_world_2d()->get_navigation_map()); + NavigationServer2D::get_singleton()->agent_set_callback(agent, this, "_avoidance_done"); } - set_physics_process_internal(true); } break; case NOTIFICATION_EXIT_TREE: { agent_parent = nullptr; - set_navigation(nullptr); set_physics_process_internal(false); } break; case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { if (agent_parent) { - - NavigationServer2D::get_singleton()->agent_set_position(agent, agent_parent->get_global_transform().get_origin()); - if (!target_reached) { - if (distance_to_target() < target_desired_distance) { - emit_signal("target_reached"); - target_reached = true; - } - } + NavigationServer2D::get_singleton()->agent_set_position(agent, agent_parent->get_global_position()); + _check_distance_to_target(); } } break; } } -NavigationAgent2D::NavigationAgent2D() : - agent_parent(nullptr), - navigation(nullptr), - agent(RID()), - target_desired_distance(1.0), - path_max_distance(3.0), - velocity_submitted(false), - target_reached(false), - navigation_finished(true) { +NavigationAgent2D::NavigationAgent2D() { agent = NavigationServer2D::get_singleton()->agent_create(); set_neighbor_dist(500.0); set_max_neighbors(10); @@ -155,22 +123,13 @@ NavigationAgent2D::~NavigationAgent2D() { agent = RID(); // Pointless } -void NavigationAgent2D::set_navigation(Navigation2D *p_nav) { - if (navigation == p_nav) - return; // Pointless - - navigation = p_nav; - NavigationServer2D::get_singleton()->agent_set_map(agent, navigation == nullptr ? RID() : navigation->get_rid()); -} - -void NavigationAgent2D::set_navigation_node(Node *p_nav) { - Navigation2D *nav = Object::cast_to<Navigation2D>(p_nav); - ERR_FAIL_COND(nav == nullptr); - set_navigation(nav); +void NavigationAgent2D::set_navigable_layers(uint32_t p_layers) { + navigable_layers = p_layers; + update_navigation(); } -Node *NavigationAgent2D::get_navigation_node() const { - return Object::cast_to<Node>(navigation); +uint32_t NavigationAgent2D::get_navigable_layers() const { + return navigable_layers; } void NavigationAgent2D::set_target_desired_distance(real_t p_dd) { @@ -226,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]; } @@ -234,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 { @@ -275,26 +234,33 @@ void NavigationAgent2D::_avoidance_done(Vector3 p_new_velocity) { } velocity_submitted = false; - emit_signal("velocity_computed", velocity); + emit_signal(SNAME("velocity_computed"), velocity); } -String NavigationAgent2D::get_configuration_warning() const { +TypedArray<String> NavigationAgent2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); + if (!Object::cast_to<Node2D>(get_parent())) { - return TTR("The NavigationAgent2D can be used only under a Node2D node"); + warnings.push_back(TTR("The NavigationAgent2D can be used only under a Node2D node")); } - return String(); + return warnings; } void NavigationAgent2D::update_navigation() { - - if (agent_parent == nullptr) return; - if (navigation == nullptr) return; - if (update_frame_id == Engine::get_singleton()->get_physics_frames()) return; + if (agent_parent == nullptr) { + return; + } + if (!agent_parent->is_inside_tree()) { + return; + } + if (update_frame_id == Engine::get_singleton()->get_physics_frames()) { + return; + } 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; @@ -308,7 +274,7 @@ void NavigationAgent2D::update_navigation() { Vector2 segment[2]; segment[0] = navigation_path[nav_path_index - 1]; segment[1] = navigation_path[nav_path_index]; - Vector2 p = Geometry::get_closest_point_to_segment_2d(o, segment); + Vector2 p = Geometry2D::get_closest_point_to_segment(o, segment); if (o.distance_to(p) >= path_max_distance) { // To faraway, reload path reload_path = true; @@ -317,14 +283,15 @@ void NavigationAgent2D::update_navigation() { } if (reload_path) { - navigation_path = NavigationServer2D::get_singleton()->map_get_path(navigation->get_rid(), o, target_location, true); + 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) + if (navigation_path.size() == 0) { return; + } // Check if we can advance the navigation path if (navigation_finished == false) { @@ -332,11 +299,21 @@ void NavigationAgent2D::update_navigation() { while (o.distance_to(navigation_path[nav_path_index]) < target_desired_distance) { nav_path_index += 1; if (nav_path_index == navigation_path.size()) { + _check_distance_to_target(); nav_path_index -= 1; navigation_finished = true; - emit_signal("navigation_finished"); + emit_signal(SNAME("navigation_finished")); break; } } } } + +void NavigationAgent2D::_check_distance_to_target() { + if (!target_reached) { + if (distance_to_target() < target_desired_distance) { + 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 26eccfc949..052cd78a56 100644 --- a/scene/2d/navigation_agent_2d.h +++ b/scene/2d/navigation_agent_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,38 +31,37 @@ #ifndef NAVIGATION_AGENT_2D_H #define NAVIGATION_AGENT_2D_H -#include "core/vector.h" #include "scene/main/node.h" class Node2D; -class Navigation2D; class NavigationAgent2D : public Node { GDCLASS(NavigationAgent2D, Node); - Node2D *agent_parent; - Navigation2D *navigation; + Node2D *agent_parent = nullptr; RID agent; - real_t target_desired_distance; + uint32_t navigable_layers = 1; + + real_t target_desired_distance = 1.0; real_t radius; real_t neighbor_dist; int max_neighbors; real_t time_horizon; real_t max_speed; - real_t path_max_distance; + real_t path_max_distance = 3.0; Vector2 target_location; Vector<Vector2> navigation_path; int nav_path_index; - bool velocity_submitted; + bool velocity_submitted = false; Vector2 prev_safe_velocity; /// The submitted target velocity Vector2 target_velocity; - bool target_reached; - bool navigation_finished; + bool target_reached = false; + bool navigation_finished = true; // No initialized on purpose uint32_t update_frame_id; @@ -74,18 +73,13 @@ public: NavigationAgent2D(); virtual ~NavigationAgent2D(); - void set_navigation(Navigation2D *p_nav); - const Navigation2D *get_navigation() const { - return navigation; - } - - void set_navigation_node(Node *p_nav); - Node *get_navigation_node() const; - RID get_rid() const { return agent; } + void set_navigable_layers(uint32_t p_layers); + uint32_t get_navigable_layers() const; + void set_target_desired_distance(real_t p_dd); real_t get_target_desired_distance() const { return target_desired_distance; @@ -141,10 +135,11 @@ public: void set_velocity(Vector2 p_velocity); void _avoidance_done(Vector3 p_new_velocity); - virtual String get_configuration_warning() const; + TypedArray<String> get_configuration_warnings() const override; private: void update_navigation(); + void _check_distance_to_target(); }; #endif diff --git a/scene/2d/navigation_obstacle_2d.cpp b/scene/2d/navigation_obstacle_2d.cpp index 50d02ca507..0a105826c0 100644 --- a/scene/2d/navigation_obstacle_2d.cpp +++ b/scene/2d/navigation_obstacle_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,56 +31,35 @@ #include "navigation_obstacle_2d.h" #include "scene/2d/collision_shape_2d.h" -#include "scene/2d/navigation_2d.h" -#include "scene/2d/physics_body_2d.h" #include "servers/navigation_server_2d.h" void NavigationObstacle2D::_bind_methods() { - - ClassDB::bind_method(D_METHOD("set_navigation", "navigation"), &NavigationObstacle2D::set_navigation_node); - ClassDB::bind_method(D_METHOD("get_navigation"), &NavigationObstacle2D::get_navigation_node); } void NavigationObstacle2D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_READY: { - - update_agent_shape(); - - // Search the navigation node and set it - { - Navigation2D *nav = nullptr; - Node *p = get_parent(); - while (p != nullptr) { - nav = Object::cast_to<Navigation2D>(p); - if (nav != nullptr) - p = nullptr; - else - p = p->get_parent(); - } - - set_navigation(nav); - } - set_physics_process_internal(true); } break; case NOTIFICATION_EXIT_TREE: { - set_navigation(nullptr); set_physics_process_internal(false); } break; + case NOTIFICATION_PARENTED: { + parent_node2d = Object::cast_to<Node2D>(get_parent()); + update_agent_shape(); + } break; + case NOTIFICATION_UNPARENTED: { + parent_node2d = nullptr; + } break; case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { - Node2D *node = Object::cast_to<Node2D>(get_parent()); - if (node) { - NavigationServer2D::get_singleton()->agent_set_position(agent, node->get_global_transform().get_origin()); + if (parent_node2d) { + NavigationServer2D::get_singleton()->agent_set_position(agent, parent_node2d->get_global_position()); } - } break; } } -NavigationObstacle2D::NavigationObstacle2D() : - navigation(nullptr), - agent(RID()) { +NavigationObstacle2D::NavigationObstacle2D() { agent = NavigationServer2D::get_singleton()->agent_create(); } @@ -89,66 +68,48 @@ NavigationObstacle2D::~NavigationObstacle2D() { agent = RID(); // Pointless } -void NavigationObstacle2D::set_navigation(Navigation2D *p_nav) { - if (navigation == p_nav) - return; // Pointless +TypedArray<String> NavigationObstacle2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); - navigation = p_nav; - NavigationServer2D::get_singleton()->agent_set_map(agent, navigation == nullptr ? RID() : navigation->get_rid()); -} - -void NavigationObstacle2D::set_navigation_node(Node *p_nav) { - Navigation2D *nav = Object::cast_to<Navigation2D>(p_nav); - ERR_FAIL_COND(nav == nullptr); - set_navigation(nav); -} - -Node *NavigationObstacle2D::get_navigation_node() const { - return Object::cast_to<Node>(navigation); -} - -String NavigationObstacle2D::get_configuration_warning() const { if (!Object::cast_to<Node2D>(get_parent())) { - return TTR("The NavigationObstacle2D only serves to provide collision avoidance to a Node2D object."); + warnings.push_back(TTR("The NavigationObstacle2D only serves to provide collision avoidance to a Node2D object.")); } - return String(); + return warnings; } void NavigationObstacle2D::update_agent_shape() { - Node *node = get_parent(); - - // Estimate the radius of this physics body - real_t radius = 0.0; - for (int i(0); i < node->get_child_count(); i++) { - // For each collision shape - CollisionShape2D *cs = Object::cast_to<CollisionShape2D>(node->get_child(i)); - if (cs) { - // Take the distance between the Body center to the shape center - real_t r = cs->get_transform().get_origin().length(); - if (cs->get_shape().is_valid()) { - // and add the enclosing shape radius - r += cs->get_shape()->get_enclosing_radius(); + if (parent_node2d) { + // Estimate the radius of this physics body + real_t radius = 0.0; + for (int i(0); i < parent_node2d->get_child_count(); i++) { + // For each collision shape + CollisionShape2D *cs = Object::cast_to<CollisionShape2D>(parent_node2d->get_child(i)); + if (cs) { + // Take the distance between the Body center to the shape center + real_t r = cs->get_transform().get_origin().length(); + if (cs->get_shape().is_valid()) { + // and add the enclosing shape radius + r += cs->get_shape()->get_enclosing_radius(); + } + Size2 s = cs->get_global_scale(); + r *= MAX(s.x, s.y); + // Takes the biggest radius + radius = MAX(radius, r); } - Size2 s = cs->get_global_transform().get_scale(); - r *= MAX(s.x, s.y); - // Takes the biggest radius - radius = MAX(radius, r); } - } - Node2D *node_2d = Object::cast_to<Node2D>(node); - if (node_2d) { - Vector2 s = node_2d->get_global_transform().get_scale(); + Vector2 s = parent_node2d->get_global_scale(); radius *= MAX(s.x, s.y); - } - if (radius == 0.0) - radius = 1.0; // Never a 0 radius + if (radius == 0.0) { + radius = 1.0; // Never a 0 radius + } - // Initialize the Agent as an object - NavigationServer2D::get_singleton()->agent_set_neighbor_dist(agent, 0.0); - NavigationServer2D::get_singleton()->agent_set_max_neighbors(agent, 0); - NavigationServer2D::get_singleton()->agent_set_time_horizon(agent, 0.0); - NavigationServer2D::get_singleton()->agent_set_radius(agent, radius); - NavigationServer2D::get_singleton()->agent_set_max_speed(agent, 0.0); + // Initialize the Agent as an object + NavigationServer2D::get_singleton()->agent_set_neighbor_dist(agent, 0.0); + NavigationServer2D::get_singleton()->agent_set_max_neighbors(agent, 0); + NavigationServer2D::get_singleton()->agent_set_time_horizon(agent, 0.0); + NavigationServer2D::get_singleton()->agent_set_radius(agent, radius); + NavigationServer2D::get_singleton()->agent_set_max_speed(agent, 0.0); + } } diff --git a/scene/2d/navigation_obstacle_2d.h b/scene/2d/navigation_obstacle_2d.h index 3935fe1bc5..9cffc2c0c3 100644 --- a/scene/2d/navigation_obstacle_2d.h +++ b/scene/2d/navigation_obstacle_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,15 +31,13 @@ #ifndef NAVIGATION_OBSTACLE_2D_H #define NAVIGATION_OBSTACLE_2D_H +#include "scene/2d/node_2d.h" #include "scene/main/node.h" -class Navigation2D; - class NavigationObstacle2D : public Node { GDCLASS(NavigationObstacle2D, Node); - Navigation2D *navigation; - + Node2D *parent_node2d = nullptr; RID agent; protected: @@ -50,19 +48,11 @@ public: NavigationObstacle2D(); virtual ~NavigationObstacle2D(); - void set_navigation(Navigation2D *p_nav); - const Navigation2D *get_navigation() const { - return navigation; - } - - void set_navigation_node(Node *p_nav); - Node *get_navigation_node() const; - RID get_rid() const { return agent; } - virtual String get_configuration_warning() const; + TypedArray<String> get_configuration_warnings() const override; private: void update_agent_shape(); diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp index d77fd5b097..72ea6541e3 100644 --- a/scene/2d/navigation_region_2d.cpp +++ b/scene/2d/navigation_region_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,16 +31,14 @@ #include "navigation_region_2d.h" #include "core/core_string_names.h" -#include "core/engine.h" +#include "core/math/geometry_2d.h" #include "core/os/mutex.h" -#include "navigation_2d.h" #include "servers/navigation_server_2d.h" -#include "thirdparty/misc/triangulator.h" +#include "thirdparty/misc/polypartition.h" #ifdef TOOLS_ENABLED Rect2 NavigationPolygon::_edit_get_rect() const { - if (rect_cache_dirty) { item_rect = Rect2(); bool first = true; @@ -48,8 +46,9 @@ Rect2 NavigationPolygon::_edit_get_rect() const { for (int i = 0; i < outlines.size(); i++) { const Vector<Vector2> &outline = outlines[i]; const int outline_size = outline.size(); - if (outline_size < 3) + if (outline_size < 3) { continue; + } const Vector2 *p = outline.ptr(); for (int j = 0; j < outline_size; j++) { if (first) { @@ -67,21 +66,21 @@ Rect2 NavigationPolygon::_edit_get_rect() const { } bool NavigationPolygon::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { - for (int i = 0; i < outlines.size(); i++) { const Vector<Vector2> &outline = outlines[i]; const int outline_size = outline.size(); - if (outline_size < 3) + if (outline_size < 3) { continue; - if (Geometry::is_point_in_polygon(p_point, Variant(outline))) + } + if (Geometry2D::is_point_in_polygon(p_point, Variant(outline))) { return true; + } } return false; } #endif void NavigationPolygon::set_vertices(const Vector<Vector2> &p_vertices) { - { MutexLock lock(navmesh_generation); navmesh.unref(); @@ -91,12 +90,10 @@ void NavigationPolygon::set_vertices(const Vector<Vector2> &p_vertices) { } Vector<Vector2> NavigationPolygon::get_vertices() const { - return vertices; } -void NavigationPolygon::_set_polygons(const Array &p_array) { - +void NavigationPolygon::_set_polygons(const TypedArray<Vector<int32_t>> &p_array) { { MutexLock lock(navmesh_generation); navmesh.unref(); @@ -108,7 +105,6 @@ void NavigationPolygon::_set_polygons(const Array &p_array) { } Array NavigationPolygon::_get_polygons() const { - Array ret; ret.resize(polygons.size()); for (int i = 0; i < ret.size(); i++) { @@ -118,8 +114,7 @@ Array NavigationPolygon::_get_polygons() const { return ret; } -void NavigationPolygon::_set_outlines(const Array &p_array) { - +void NavigationPolygon::_set_outlines(const TypedArray<Vector<Vector2>> &p_array) { outlines.resize(p_array.size()); for (int i = 0; i < p_array.size(); i++) { outlines.write[i] = p_array[i]; @@ -128,7 +123,6 @@ void NavigationPolygon::_set_outlines(const Array &p_array) { } Array NavigationPolygon::_get_outlines() const { - Array ret; ret.resize(outlines.size()); for (int i = 0; i < ret.size(); i++) { @@ -139,7 +133,6 @@ Array NavigationPolygon::_get_outlines() const { } void NavigationPolygon::add_polygon(const Vector<int> &p_polygon) { - Polygon polygon; polygon.indices = p_polygon; polygons.push_back(polygon); @@ -150,22 +143,20 @@ void NavigationPolygon::add_polygon(const Vector<int> &p_polygon) { } void NavigationPolygon::add_outline_at_index(const Vector<Vector2> &p_outline, int p_index) { - outlines.insert(p_index, p_outline); rect_cache_dirty = true; } int NavigationPolygon::get_polygon_count() const { - return polygons.size(); } -Vector<int> NavigationPolygon::get_polygon(int p_idx) { +Vector<int> NavigationPolygon::get_polygon(int p_idx) { ERR_FAIL_INDEX_V(p_idx, polygons.size(), Vector<int>()); return polygons[p_idx].indices; } -void NavigationPolygon::clear_polygons() { +void NavigationPolygon::clear_polygons() { polygons.clear(); { MutexLock lock(navmesh_generation); @@ -177,7 +168,7 @@ Ref<NavigationMesh> NavigationPolygon::get_mesh() { MutexLock lock(navmesh_generation); if (navmesh.is_null()) { - navmesh.instance(); + navmesh.instantiate(); Vector<Vector3> verts; { verts.resize(get_vertices().size()); @@ -200,13 +191,11 @@ Ref<NavigationMesh> NavigationPolygon::get_mesh() { } void NavigationPolygon::add_outline(const Vector<Vector2> &p_outline) { - outlines.push_back(p_outline); rect_cache_dirty = true; } int NavigationPolygon::get_outline_count() const { - return outlines.size(); } @@ -217,7 +206,6 @@ void NavigationPolygon::set_outline(int p_idx, const Vector<Vector2> &p_outline) } void NavigationPolygon::remove_outline(int p_idx) { - ERR_FAIL_INDEX(p_idx, outlines.size()); outlines.remove(p_idx); rect_cache_dirty = true; @@ -229,26 +217,25 @@ Vector<Vector2> NavigationPolygon::get_outline(int p_idx) const { } void NavigationPolygon::clear_outlines() { - outlines.clear(); rect_cache_dirty = true; } -void NavigationPolygon::make_polygons_from_outlines() { +void NavigationPolygon::make_polygons_from_outlines() { { MutexLock lock(navmesh_generation); navmesh.unref(); } - List<TriangulatorPoly> in_poly, out_poly; + List<TPPLPoly> in_poly, out_poly; Vector2 outside_point(-1e10, -1e10); for (int i = 0; i < outlines.size(); i++) { - Vector<Vector2> ol = outlines[i]; int olsize = ol.size(); - if (olsize < 3) + if (olsize < 3) { continue; + } const Vector2 *r = ol.ptr(); for (int j = 0; j < olsize; j++) { outside_point.x = MAX(r[j].x, outside_point.x); @@ -259,29 +246,29 @@ void NavigationPolygon::make_polygons_from_outlines() { outside_point += Vector2(0.7239784, 0.819238); //avoid precision issues for (int i = 0; i < outlines.size(); i++) { - Vector<Vector2> ol = outlines[i]; int olsize = ol.size(); - if (olsize < 3) + if (olsize < 3) { continue; + } const Vector2 *r = ol.ptr(); int interscount = 0; //test if this is an outer outline for (int k = 0; k < outlines.size(); k++) { - - if (i == k) + if (i == k) { continue; //no self intersect + } Vector<Vector2> ol2 = outlines[k]; int olsize2 = ol2.size(); - if (olsize2 < 3) + if (olsize2 < 3) { continue; + } const Vector2 *r2 = ol2.ptr(); for (int l = 0; l < olsize2; l++) { - - if (Geometry::segment_intersects_segment_2d(r[0], outside_point, r2[l], r2[(l + 1) % olsize2], nullptr)) { + if (Geometry2D::segment_intersects_segment(r[0], outside_point, r2[l], r2[(l + 1) % olsize2], nullptr)) { interscount++; } } @@ -289,23 +276,23 @@ void NavigationPolygon::make_polygons_from_outlines() { bool outer = (interscount % 2) == 0; - TriangulatorPoly tp; + TPPLPoly tp; tp.Init(olsize); for (int j = 0; j < olsize; j++) { tp[j] = r[j]; } - if (outer) - tp.SetOrientation(TRIANGULATOR_CCW); - else { - tp.SetOrientation(TRIANGULATOR_CW); + if (outer) { + tp.SetOrientation(TPPL_ORIENTATION_CCW); + } else { + tp.SetOrientation(TPPL_ORIENTATION_CW); tp.SetHole(true); } in_poly.push_back(tp); } - TriangulatorPartition tpart; + TPPLPartition tpart; if (tpart.ConvexPartition_HM(&in_poly, &out_poly) == 0) { //failed! ERR_PRINT("NavigationPolygon: Convex partition failed!"); return; @@ -315,14 +302,12 @@ void NavigationPolygon::make_polygons_from_outlines() { vertices.resize(0); Map<Vector2, int> points; - for (List<TriangulatorPoly>::Element *I = out_poly.front(); I; I = I->next()) { - - TriangulatorPoly &tp = I->get(); + for (List<TPPLPoly>::Element *I = out_poly.front(); I; I = I->next()) { + TPPLPoly &tp = I->get(); struct Polygon p; for (int64_t i = 0; i < tp.GetNumPoints(); i++) { - Map<Vector2, int>::Element *E = points.find(tp[i]); if (!E) { E = points.insert(tp[i], vertices.size()); @@ -338,7 +323,6 @@ void NavigationPolygon::make_polygons_from_outlines() { } void NavigationPolygon::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_vertices", "vertices"), &NavigationPolygon::set_vertices); ClassDB::bind_method(D_METHOD("get_vertices"), &NavigationPolygon::get_vertices); @@ -367,98 +351,75 @@ void NavigationPolygon::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_outlines", "_get_outlines"); } -NavigationPolygon::NavigationPolygon() : - rect_cache_dirty(true) { -} - -NavigationPolygon::~NavigationPolygon() { -} - void NavigationRegion2D::set_enabled(bool p_enabled) { - - if (enabled == p_enabled) + if (enabled == p_enabled) { return; + } enabled = p_enabled; - if (!is_inside_tree()) + if (!is_inside_tree()) { return; + } if (!enabled) { - NavigationServer2D::get_singleton()->region_set_map(region, RID()); + NavigationServer2D::get_singleton()->disconnect("map_changed", callable_mp(this, &NavigationRegion2D::_map_changed)); } else { - - if (navigation) { - - NavigationServer2D::get_singleton()->region_set_map(region, navigation->get_rid()); - } + NavigationServer2D::get_singleton()->region_set_map(region, get_world_2d()->get_navigation_map()); + NavigationServer2D::get_singleton()->connect("map_changed", callable_mp(this, &NavigationRegion2D::_map_changed)); } - if (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_navigation_hint()) + if (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_navigation_hint()) { update(); + } } bool NavigationRegion2D::is_enabled() const { - return enabled; } +void NavigationRegion2D::set_layers(uint32_t p_layers) { + NavigationServer2D::get_singleton()->region_set_layers(region, p_layers); +} + +uint32_t NavigationRegion2D::get_layers() const { + return NavigationServer2D::get_singleton()->region_get_layers(region); +} + ///////////////////////////// #ifdef TOOLS_ENABLED Rect2 NavigationRegion2D::_edit_get_rect() const { - return navpoly.is_valid() ? navpoly->_edit_get_rect() : Rect2(); } bool NavigationRegion2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { - return navpoly.is_valid() ? navpoly->_edit_is_selected_on_click(p_point, p_tolerance) : false; } #endif void NavigationRegion2D::_notification(int p_what) { - switch (p_what) { case NOTIFICATION_ENTER_TREE: { - - Node2D *c = this; - while (c) { - - navigation = Object::cast_to<Navigation2D>(c); - if (navigation) { - - if (enabled) { - - NavigationServer2D::get_singleton()->region_set_map(region, navigation->get_rid()); - } - break; - } - - c = Object::cast_to<Node2D>(c->get_parent()); + if (enabled) { + NavigationServer2D::get_singleton()->region_set_map(region, get_world_2d()->get_navigation_map()); + NavigationServer2D::get_singleton()->connect("map_changed", callable_mp(this, &NavigationRegion2D::_map_changed)); } - } break; case NOTIFICATION_TRANSFORM_CHANGED: { - NavigationServer2D::get_singleton()->region_set_transform(region, get_global_transform()); - } break; case NOTIFICATION_EXIT_TREE: { - - if (navigation) { - - NavigationServer2D::get_singleton()->region_set_map(region, RID()); + NavigationServer2D::get_singleton()->region_set_map(region, RID()); + if (enabled) { + NavigationServer2D::get_singleton()->disconnect("map_changed", callable_mp(this, &NavigationRegion2D::_map_changed)); } - navigation = nullptr; } break; case NOTIFICATION_DRAW: { - if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_navigation_hint()) && navpoly.is_valid()) { - Vector<Vector2> verts = navpoly->get_vertices(); - int vsize = verts.size(); - if (vsize < 3) + if (verts.size() < 3) { return; + } Color color; if (enabled) { @@ -466,42 +427,53 @@ void NavigationRegion2D::_notification(int p_what) { } else { color = get_tree()->get_debug_navigation_disabled_color(); } - Vector<Color> colors; - Vector<Vector2> vertices; - vertices.resize(vsize); - colors.resize(vsize); - { - const Vector2 *vr = verts.ptr(); - for (int i = 0; i < vsize; i++) { - vertices.write[i] = vr[i]; - colors.write[i] = color; - } - } + Color doors_color = color.lightened(0.2); - Vector<int> indices; + RandomPCG rand; 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], verts.size()); + vertices.write[j] = verts[polygon[j]]; + } - for (int j = 2; j < polygon.size(); 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); - int kofs[3] = { 0, j - 1, j }; - for (int k = 0; k < 3; k++) { + RS::get_singleton()->canvas_item_add_polygon(get_canvas_item(), vertices, colors); + } - int idx = polygon[kofs[k]]; - ERR_FAIL_INDEX(idx, vsize); - indices.push_back(idx); - } - } + // Draw the region + Transform2D xform = get_global_transform(); + const NavigationServer2D *ns = NavigationServer2D::get_singleton(); + 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); + a = xform.affine_inverse().xform(a); + Vector2 b = ns->region_get_connection_pathway_end(region, i); + b = xform.affine_inverse().xform(b); + draw_line(a, b, doors_color); + + // Draw a circle to illustrate the margins. + 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); } - RS::get_singleton()->canvas_item_add_triangle_array(get_canvas_item(), indices, vertices, colors); } } break; } } void NavigationRegion2D::set_navigation_polygon(const Ref<NavigationPolygon> &p_navpoly) { - if (p_navpoly == navpoly) { return; } @@ -518,63 +490,56 @@ void NavigationRegion2D::set_navigation_polygon(const Ref<NavigationPolygon> &p_ } _navpoly_changed(); - _change_notify("navpoly"); - update_configuration_warning(); + update_configuration_warnings(); } Ref<NavigationPolygon> NavigationRegion2D::get_navigation_polygon() const { - return navpoly; } void NavigationRegion2D::_navpoly_changed() { - - if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_navigation_hint())) + if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_navigation_hint())) { + update(); + } +} +void NavigationRegion2D::_map_changed(RID p_map) { + if (enabled && get_world_2d()->get_navigation_map() == p_map) { update(); + } } -String NavigationRegion2D::get_configuration_warning() const { - - if (!is_visible_in_tree() || !is_inside_tree()) - return String(); - - if (!navpoly.is_valid()) { - return TTR("A NavigationPolygon resource must be set or created for this node to work. Please set a property or draw a polygon."); - } - const Node2D *c = this; - while (c) { +TypedArray<String> NavigationRegion2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node2D::get_configuration_warnings(); - if (Object::cast_to<Navigation2D>(c)) { - return String(); + if (is_visible_in_tree() && is_inside_tree()) { + if (!navpoly.is_valid()) { + warnings.push_back(TTR("A NavigationMesh resource must be set or created for this node to work. Please set a property or draw a polygon.")); } - - c = Object::cast_to<Node2D>(c->get_parent()); } - return TTR("NavigationRegion2D must be a child or grandchild to a Navigation2D node. It only provides navigation data."); + return warnings; } void NavigationRegion2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_navigation_polygon", "navpoly"), &NavigationRegion2D::set_navigation_polygon); ClassDB::bind_method(D_METHOD("get_navigation_polygon"), &NavigationRegion2D::get_navigation_polygon); ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &NavigationRegion2D::set_enabled); ClassDB::bind_method(D_METHOD("is_enabled"), &NavigationRegion2D::is_enabled); + ClassDB::bind_method(D_METHOD("set_layers", "layers"), &NavigationRegion2D::set_layers); + ClassDB::bind_method(D_METHOD("get_layers"), &NavigationRegion2D::get_layers); + ClassDB::bind_method(D_METHOD("_navpoly_changed"), &NavigationRegion2D::_navpoly_changed); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "navpoly", PROPERTY_HINT_RESOURCE_TYPE, "NavigationPolygon"), "set_navigation_polygon", "get_navigation_polygon"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "layers", PROPERTY_HINT_LAYERS_2D_NAVIGATION), "set_layers", "get_layers"); } NavigationRegion2D::NavigationRegion2D() { - - enabled = true; set_notify_transform(true); region = NavigationServer2D::get_singleton()->region_create(); - - navigation = nullptr; } NavigationRegion2D::~NavigationRegion2D() { diff --git a/scene/2d/navigation_region_2d.h b/scene/2d/navigation_region_2d.h index 73e056a353..2db8d70791 100644 --- a/scene/2d/navigation_region_2d.h +++ b/scene/2d/navigation_region_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -35,7 +35,6 @@ #include "scene/resources/navigation_mesh.h" class NavigationPolygon : public Resource { - GDCLASS(NavigationPolygon, Resource); Vector<Vector2> vertices; @@ -46,7 +45,7 @@ class NavigationPolygon : public Resource { Vector<Vector<Vector2>> outlines; mutable Rect2 item_rect; - mutable bool rect_cache_dirty; + mutable bool rect_cache_dirty = true; Mutex navmesh_generation; // Navigation mesh @@ -55,10 +54,10 @@ class NavigationPolygon : public Resource { protected: static void _bind_methods(); - void _set_polygons(const Array &p_array); + void _set_polygons(const TypedArray<Vector<int32_t>> &p_array); Array _get_polygons() const; - void _set_outlines(const Array &p_array); + void _set_outlines(const TypedArray<Vector<Vector2>> &p_array); Array _get_outlines() const; public: @@ -88,22 +87,19 @@ public: Ref<NavigationMesh> get_mesh(); - NavigationPolygon(); - ~NavigationPolygon(); + NavigationPolygon() {} + ~NavigationPolygon() {} }; -class Navigation2D; - class NavigationRegion2D : public Node2D { - GDCLASS(NavigationRegion2D, Node2D); - bool enabled; + bool enabled = true; RID region; - Navigation2D *navigation; Ref<NavigationPolygon> navpoly; void _navpoly_changed(); + void _map_changed(RID p_RID); protected: void _notification(int p_what); @@ -111,17 +107,20 @@ protected: public: #ifdef TOOLS_ENABLED - virtual Rect2 _edit_get_rect() const; - virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const; + virtual Rect2 _edit_get_rect() const override; + virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override; #endif void set_enabled(bool p_enabled); bool is_enabled() const; + void set_layers(uint32_t p_layers); + uint32_t get_layers() const; + void set_navigation_polygon(const Ref<NavigationPolygon> &p_navpoly); Ref<NavigationPolygon> get_navigation_polygon() const; - String get_configuration_warning() const; + TypedArray<String> get_configuration_warnings() const override; NavigationRegion2D(); ~NavigationRegion2D(); diff --git a/scene/2d/node_2d.cpp b/scene/2d/node_2d.cpp index ac8a77b6cb..6a8788ee6e 100644 --- a/scene/2d/node_2d.cpp +++ b/scene/2d/node_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,33 +30,24 @@ #include "node_2d.h" -#include "core/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; state["position"] = get_position(); state["rotation"] = get_rotation(); state["scale"] = get_scale(); + state["skew"] = get_skew(); return state; } void Node2D::_edit_set_state(const Dictionary &p_state) { - pos = p_state["position"]; angle = p_state["rotation"]; _scale = p_state["scale"]; + skew = p_state["skew"]; _update_transform(); - _change_notify("rotation"); - _change_notify("rotation_degrees"); - _change_notify("scale"); - _change_notify("position"); } void Node2D::_edit_set_position(const Point2 &p_position) { @@ -75,14 +66,12 @@ Size2 Node2D::_edit_get_scale() const { return _scale; } -void Node2D::_edit_set_rotation(float p_rotation) { +void Node2D::_edit_set_rotation(real_t p_rotation) { angle = p_rotation; _update_transform(); - _change_notify("rotation"); - _change_notify("rotation_degrees"); } -float Node2D::_edit_get_rotation() const { +real_t Node2D::_edit_get_rotation() const { return angle; } @@ -96,168 +85,169 @@ void Node2D::_edit_set_rect(const Rect2 &p_edit_rect) { Rect2 r = _edit_get_rect(); Vector2 zero_offset; - if (r.size.x != 0) + if (r.size.x != 0) { zero_offset.x = -r.position.x / r.size.x; - if (r.size.y != 0) + } + if (r.size.y != 0) { zero_offset.y = -r.position.y / r.size.y; + } Size2 new_scale(1, 1); - if (r.size.x != 0) + if (r.size.x != 0) { new_scale.x = p_edit_rect.size.x / r.size.x; - if (r.size.y != 0) + } + if (r.size.y != 0) { new_scale.y = p_edit_rect.size.y / r.size.y; + } Point2 new_pos = p_edit_rect.position + p_edit_rect.size * zero_offset; Transform2D postxf; - postxf.set_rotation_and_scale(angle, _scale); + postxf.set_rotation_scale_and_skew(angle, _scale, skew); new_pos = postxf.xform(new_pos); pos += new_pos; _scale *= new_scale; _update_transform(); - _change_notify("scale"); - _change_notify("position"); } #endif void Node2D::_update_xform_values() { - pos = _mat.elements[2]; angle = _mat.get_rotation(); _scale = _mat.get_scale(); + skew = _mat.get_skew(); _xform_dirty = false; } void Node2D::_update_transform() { - - _mat.set_rotation_and_scale(angle, _scale); + _mat.set_rotation_scale_and_skew(angle, _scale, skew); _mat.elements[2] = pos; RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), _mat); - if (!is_inside_tree()) + if (!is_inside_tree()) { return; + } _notify_transform(); } void Node2D::set_position(const Point2 &p_pos) { - - if (_xform_dirty) + if (_xform_dirty) { ((Node2D *)this)->_update_xform_values(); + } pos = p_pos; _update_transform(); - _change_notify("position"); } -void Node2D::set_rotation(float p_radians) { - - if (_xform_dirty) +void Node2D::set_rotation(real_t p_radians) { + if (_xform_dirty) { ((Node2D *)this)->_update_xform_values(); + } angle = p_radians; _update_transform(); - _change_notify("rotation"); - _change_notify("rotation_degrees"); } -void Node2D::set_rotation_degrees(float p_degrees) { - - set_rotation(Math::deg2rad(p_degrees)); +void Node2D::set_skew(real_t p_radians) { + if (_xform_dirty) { + ((Node2D *)this)->_update_xform_values(); + } + skew = p_radians; + _update_transform(); } void Node2D::set_scale(const Size2 &p_scale) { - - if (_xform_dirty) + if (_xform_dirty) { ((Node2D *)this)->_update_xform_values(); + } _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(); - _change_notify("scale"); } Point2 Node2D::get_position() const { - - if (_xform_dirty) + if (_xform_dirty) { ((Node2D *)this)->_update_xform_values(); + } return pos; } -float Node2D::get_rotation() const { - if (_xform_dirty) +real_t Node2D::get_rotation() const { + if (_xform_dirty) { ((Node2D *)this)->_update_xform_values(); + } return angle; } -float Node2D::get_rotation_degrees() const { +real_t Node2D::get_skew() const { + if (_xform_dirty) { + ((Node2D *)this)->_update_xform_values(); + } - return Math::rad2deg(get_rotation()); + return skew; } Size2 Node2D::get_scale() const { - if (_xform_dirty) + if (_xform_dirty) { ((Node2D *)this)->_update_xform_values(); + } return _scale; } Transform2D Node2D::get_transform() const { - return _mat; } -void Node2D::rotate(float p_radians) { - +void Node2D::rotate(real_t p_radians) { set_rotation(get_rotation() + p_radians); } void Node2D::translate(const Vector2 &p_amount) { - set_position(get_position() + p_amount); } void Node2D::global_translate(const Vector2 &p_amount) { - set_global_position(get_global_position() + p_amount); } void Node2D::apply_scale(const Size2 &p_amount) { - set_scale(get_scale() * p_amount); } -void Node2D::move_x(float p_delta, bool p_scaled) { - +void Node2D::move_x(real_t p_delta, bool p_scaled) { Transform2D t = get_transform(); Vector2 m = t[0]; - if (!p_scaled) + if (!p_scaled) { m.normalize(); + } set_position(t[2] + m * p_delta); } -void Node2D::move_y(float p_delta, bool p_scaled) { - +void Node2D::move_y(real_t p_delta, bool p_scaled) { Transform2D t = get_transform(); Vector2 m = t[1]; - if (!p_scaled) + if (!p_scaled) { m.normalize(); + } set_position(t[2] + m * p_delta); } Point2 Node2D::get_global_position() const { - return get_global_transform().get_origin(); } void Node2D::set_global_position(const Point2 &p_pos) { - Transform2D inv; CanvasItem *pi = get_parent_item(); if (pi) { @@ -268,39 +258,25 @@ void Node2D::set_global_position(const Point2 &p_pos) { } } -float Node2D::get_global_rotation() const { - +real_t Node2D::get_global_rotation() const { return get_global_transform().get_rotation(); } -void Node2D::set_global_rotation(float p_radians) { - +void Node2D::set_global_rotation(real_t p_radians) { CanvasItem *pi = get_parent_item(); if (pi) { - const float parent_global_rot = pi->get_global_transform().get_rotation(); + const real_t parent_global_rot = pi->get_global_transform().get_rotation(); set_rotation(p_radians - parent_global_rot); } else { set_rotation(p_radians); } } -float Node2D::get_global_rotation_degrees() const { - - return Math::rad2deg(get_global_rotation()); -} - -void Node2D::set_global_rotation_degrees(float p_degrees) { - - set_global_rotation(Math::deg2rad(p_degrees)); -} - Size2 Node2D::get_global_scale() const { - return get_global_transform().get_scale(); } void Node2D::set_global_scale(const Size2 &p_scale) { - CanvasItem *pi = get_parent_item(); if (pi) { const Size2 parent_global_scale = pi->get_global_transform().get_scale(); @@ -311,98 +287,99 @@ void Node2D::set_global_scale(const Size2 &p_scale) { } void Node2D::set_transform(const Transform2D &p_transform) { - _mat = p_transform; _xform_dirty = true; RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), _mat); - if (!is_inside_tree()) + if (!is_inside_tree()) { return; + } _notify_transform(); } void Node2D::set_global_transform(const Transform2D &p_transform) { - CanvasItem *pi = get_parent_item(); - if (pi) + if (pi) { set_transform(pi->get_global_transform().affine_inverse() * p_transform); - else + } else { set_transform(p_transform); + } } void Node2D::set_z_index(int p_z) { - ERR_FAIL_COND(p_z < RS::CANVAS_ITEM_Z_MIN); ERR_FAIL_COND(p_z > RS::CANVAS_ITEM_Z_MAX); z_index = p_z; RS::get_singleton()->canvas_item_set_z_index(get_canvas_item(), z_index); - _change_notify("z_index"); } void Node2D::set_z_as_relative(bool p_enabled) { - - if (z_relative == p_enabled) + if (z_relative == p_enabled) { return; + } z_relative = p_enabled; RS::get_singleton()->canvas_item_set_z_as_relative_to_parent(get_canvas_item(), p_enabled); } bool Node2D::is_z_relative() const { - return z_relative; } int Node2D::get_z_index() const { - return z_index; } Transform2D Node2D::get_relative_transform_to_parent(const Node *p_parent) const { - - if (p_parent == this) + if (p_parent == this) { return Transform2D(); + } Node2D *parent_2d = Object::cast_to<Node2D>(get_parent()); ERR_FAIL_COND_V(!parent_2d, Transform2D()); - if (p_parent == parent_2d) + if (p_parent == parent_2d) { return get_transform(); - else + } else { return parent_2d->get_relative_transform_to_parent(p_parent) * get_transform(); + } } void Node2D::look_at(const Vector2 &p_pos) { - rotate(get_angle_to(p_pos)); } -float Node2D::get_angle_to(const Vector2 &p_pos) const { - +real_t Node2D::get_angle_to(const Vector2 &p_pos) const { return (to_local(p_pos) * get_scale()).angle(); } Point2 Node2D::to_local(Point2 p_global) const { - return get_global_transform().affine_inverse().xform(p_global); } Point2 Node2D::to_global(Point2 p_local) const { - return get_global_transform().xform(p_local); } -void Node2D::_bind_methods() { +void Node2D::set_y_sort_enabled(bool p_enabled) { + y_sort_enabled = p_enabled; + RS::get_singleton()->canvas_item_set_sort_children_by_y(get_canvas_item(), y_sort_enabled); +} + +bool Node2D::is_y_sort_enabled() const { + return y_sort_enabled; +} +void Node2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_position", "position"), &Node2D::set_position); ClassDB::bind_method(D_METHOD("set_rotation", "radians"), &Node2D::set_rotation); - ClassDB::bind_method(D_METHOD("set_rotation_degrees", "degrees"), &Node2D::set_rotation_degrees); + ClassDB::bind_method(D_METHOD("set_skew", "radians"), &Node2D::set_skew); ClassDB::bind_method(D_METHOD("set_scale", "scale"), &Node2D::set_scale); ClassDB::bind_method(D_METHOD("get_position"), &Node2D::get_position); ClassDB::bind_method(D_METHOD("get_rotation"), &Node2D::get_rotation); - ClassDB::bind_method(D_METHOD("get_rotation_degrees"), &Node2D::get_rotation_degrees); + ClassDB::bind_method(D_METHOD("get_skew"), &Node2D::get_skew); ClassDB::bind_method(D_METHOD("get_scale"), &Node2D::get_scale); ClassDB::bind_method(D_METHOD("rotate", "radians"), &Node2D::rotate); @@ -416,8 +393,6 @@ void Node2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_global_position"), &Node2D::get_global_position); ClassDB::bind_method(D_METHOD("set_global_rotation", "radians"), &Node2D::set_global_rotation); ClassDB::bind_method(D_METHOD("get_global_rotation"), &Node2D::get_global_rotation); - ClassDB::bind_method(D_METHOD("set_global_rotation_degrees", "degrees"), &Node2D::set_global_rotation_degrees); - ClassDB::bind_method(D_METHOD("get_global_rotation_degrees"), &Node2D::get_global_rotation_degrees); ClassDB::bind_method(D_METHOD("set_global_scale", "scale"), &Node2D::set_global_scale); ClassDB::bind_method(D_METHOD("get_global_scale"), &Node2D::get_global_scale); @@ -436,31 +411,25 @@ void Node2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_z_as_relative", "enable"), &Node2D::set_z_as_relative); ClassDB::bind_method(D_METHOD("is_z_relative"), &Node2D::is_z_relative); + ClassDB::bind_method(D_METHOD("set_y_sort_enabled", "enabled"), &Node2D::set_y_sort_enabled); + ClassDB::bind_method(D_METHOD("is_y_sort_enabled"), &Node2D::is_y_sort_enabled); + ClassDB::bind_method(D_METHOD("get_relative_transform_to_parent", "parent"), &Node2D::get_relative_transform_to_parent); ADD_GROUP("Transform", ""); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position"), "set_position", "get_position"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_rotation", "get_rotation"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rotation_degrees", PROPERTY_HINT_RANGE, "-360,360,0.1,or_lesser,or_greater", PROPERTY_USAGE_EDITOR), "set_rotation_degrees", "get_rotation_degrees"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position", PROPERTY_HINT_RANGE, "-99999,99999,0,or_lesser,or_greater,noslider,suffix:px"), "set_position", "get_position"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_lesser,or_greater,radians"), "set_rotation", "get_rotation"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scale"), "set_scale", "get_scale"); - ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "transform", PROPERTY_HINT_NONE, "", 0), "set_transform", "get_transform"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "skew", PROPERTY_HINT_RANGE, "-89.9,89.9,0.1,radians"), "set_skew", "get_skew"); + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_transform", "get_transform"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "global_position", PROPERTY_HINT_NONE, "", 0), "set_global_position", "get_global_position"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "global_rotation", PROPERTY_HINT_NONE, "", 0), "set_global_rotation", "get_global_rotation"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "global_rotation_degrees", PROPERTY_HINT_NONE, "", 0), "set_global_rotation_degrees", "get_global_rotation_degrees"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "global_scale", PROPERTY_HINT_NONE, "", 0), "set_global_scale", "get_global_scale"); - ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "global_transform", PROPERTY_HINT_NONE, "", 0), "set_global_transform", "get_global_transform"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "global_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_position", "get_global_position"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "global_rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_rotation", "get_global_rotation"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "global_scale", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_scale", "get_global_scale"); + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "global_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_transform", "get_global_transform"); - ADD_GROUP("Z Index", ""); + ADD_GROUP("Ordering", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "z_index", PROPERTY_HINT_RANGE, itos(RS::CANVAS_ITEM_Z_MIN) + "," + itos(RS::CANVAS_ITEM_Z_MAX) + ",1"), "set_z_index", "get_z_index"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "z_as_relative"), "set_z_as_relative", "is_z_relative"); -} - -Node2D::Node2D() { - - angle = 0; - _scale = Vector2(1, 1); - _xform_dirty = false; - z_index = 0; - z_relative = true; + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "y_sort_enabled"), "set_y_sort_enabled", "is_y_sort_enabled"); } diff --git a/scene/2d/node_2d.h b/scene/2d/node_2d.h index abed05ed0c..3e66541e32 100644 --- a/scene/2d/node_2d.h +++ b/scene/2d/node_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -34,18 +34,19 @@ #include "scene/main/canvas_item.h" class Node2D : public CanvasItem { - GDCLASS(Node2D, CanvasItem); Point2 pos; - float angle; - Size2 _scale; - int z_index; - bool z_relative; + real_t angle = 0.0; + Size2 _scale = Vector2(1, 1); + real_t skew = 0.0; + int z_index = 0; + bool z_relative = true; + bool y_sort_enabled = false; Transform2D _mat; - bool _xform_dirty; + bool _xform_dirty = false; void _update_transform(); @@ -56,56 +57,54 @@ protected: public: #ifdef TOOLS_ENABLED - virtual Dictionary _edit_get_state() const; - virtual void _edit_set_state(const Dictionary &p_state); + virtual Dictionary _edit_get_state() const override; + virtual void _edit_set_state(const Dictionary &p_state) override; - virtual void _edit_set_position(const Point2 &p_position); - virtual Point2 _edit_get_position() const; + virtual void _edit_set_position(const Point2 &p_position) override; + virtual Point2 _edit_get_position() const override; - virtual void _edit_set_scale(const Size2 &p_scale); - virtual Size2 _edit_get_scale() const; + virtual void _edit_set_scale(const Size2 &p_scale) override; + virtual Size2 _edit_get_scale() const override; - virtual void _edit_set_rotation(float p_rotation); - virtual float _edit_get_rotation() const; - virtual bool _edit_use_rotation() const; + virtual void _edit_set_rotation(real_t p_rotation) override; + virtual real_t _edit_get_rotation() const override; + virtual bool _edit_use_rotation() const override; - virtual void _edit_set_rect(const Rect2 &p_edit_rect); + virtual void _edit_set_rect(const Rect2 &p_edit_rect) override; #endif void set_position(const Point2 &p_pos); - void set_rotation(float p_radians); - void set_rotation_degrees(float p_degrees); + void set_rotation(real_t p_radians); + void set_skew(real_t p_radians); void set_scale(const Size2 &p_scale); - void rotate(float p_radians); - void move_x(float p_delta, bool p_scaled = false); - void move_y(float p_delta, bool p_scaled = false); + void rotate(real_t p_radians); + void move_x(real_t p_delta, bool p_scaled = false); + void move_y(real_t p_delta, bool p_scaled = false); void translate(const Vector2 &p_amount); void global_translate(const Vector2 &p_amount); void apply_scale(const Size2 &p_amount); Point2 get_position() const; - float get_rotation() const; - float get_rotation_degrees() const; + real_t get_rotation() const; + real_t get_skew() const; Size2 get_scale() const; Point2 get_global_position() const; - float get_global_rotation() const; - float get_global_rotation_degrees() const; + real_t get_global_rotation() const; Size2 get_global_scale() const; void set_transform(const Transform2D &p_transform); void set_global_transform(const Transform2D &p_transform); void set_global_position(const Point2 &p_pos); - void set_global_rotation(float p_radians); - void set_global_rotation_degrees(float p_degrees); + void set_global_rotation(real_t p_radians); void set_global_scale(const Size2 &p_scale); void set_z_index(int p_z); int get_z_index() const; void look_at(const Vector2 &p_pos); - float get_angle_to(const Vector2 &p_pos) const; + real_t get_angle_to(const Vector2 &p_pos) const; Point2 to_local(Point2 p_global) const; Point2 to_global(Point2 p_local) const; @@ -113,11 +112,14 @@ public: void set_z_as_relative(bool p_enabled); bool is_z_relative() const; + virtual void set_y_sort_enabled(bool p_enabled); + virtual bool is_y_sort_enabled() const; + Transform2D get_relative_transform_to_parent(const Node *p_parent) const; - Transform2D get_transform() const; + Transform2D get_transform() const override; - Node2D(); + Node2D() {} }; #endif // NODE2D_H diff --git a/scene/2d/parallax_background.cpp b/scene/2d/parallax_background.cpp index 0d5f74a265..4870ae614b 100644 --- a/scene/2d/parallax_background.cpp +++ b/scene/2d/parallax_background.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,51 +32,43 @@ #include "parallax_layer.h" void ParallaxBackground::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - group_name = "__cameras_" + itos(get_viewport().get_id()); add_to_group(group_name); } break; case NOTIFICATION_EXIT_TREE: { - remove_from_group(group_name); } break; } } void ParallaxBackground::_camera_moved(const Transform2D &p_transform, const Point2 &p_screen_offset) { - screen_offset = p_screen_offset; set_scroll_scale(p_transform.get_scale().dot(Vector2(0.5, 0.5))); set_scroll_offset(p_transform.get_origin()); } -void ParallaxBackground::set_scroll_scale(float p_scale) { - +void ParallaxBackground::set_scroll_scale(real_t p_scale) { scale = p_scale; } -float ParallaxBackground::get_scroll_scale() const { - +real_t ParallaxBackground::get_scroll_scale() const { return scale; } void ParallaxBackground::set_scroll_offset(const Point2 &p_ofs) { - offset = p_ofs; _update_scroll(); } void ParallaxBackground::_update_scroll() { - - if (!is_inside_tree()) + if (!is_inside_tree()) { return; + } Vector2 ofs = base_offset + offset * base_scale; @@ -84,103 +76,91 @@ void ParallaxBackground::_update_scroll() { ofs = -ofs; if (limit_begin.x < limit_end.x) { - - if (ofs.x < limit_begin.x) + if (ofs.x < limit_begin.x) { ofs.x = limit_begin.x; - else if (ofs.x + vps.x > limit_end.x) + } else if (ofs.x + vps.x > limit_end.x) { ofs.x = limit_end.x - vps.x; + } } if (limit_begin.y < limit_end.y) { - - if (ofs.y < limit_begin.y) + if (ofs.y < limit_begin.y) { ofs.y = limit_begin.y; - else if (ofs.y + vps.y > limit_end.y) + } else if (ofs.y + vps.y > limit_end.y) { ofs.y = limit_end.y - vps.y; + } } ofs = -ofs; final_offset = ofs; for (int i = 0; i < get_child_count(); i++) { - ParallaxLayer *l = Object::cast_to<ParallaxLayer>(get_child(i)); - if (!l) + if (!l) { continue; + } - if (ignore_camera_zoom) - l->set_base_offset_and_scale(ofs, 1.0, screen_offset); - else + if (ignore_camera_zoom) { + l->set_base_offset_and_scale((ofs + screen_offset * (scale - 1)) / scale, 1.0, screen_offset); + } else { l->set_base_offset_and_scale(ofs, scale, screen_offset); + } } } Point2 ParallaxBackground::get_scroll_offset() const { - return offset; } void ParallaxBackground::set_scroll_base_offset(const Point2 &p_ofs) { - base_offset = p_ofs; _update_scroll(); } Point2 ParallaxBackground::get_scroll_base_offset() const { - return base_offset; } void ParallaxBackground::set_scroll_base_scale(const Point2 &p_ofs) { - base_scale = p_ofs; _update_scroll(); } Point2 ParallaxBackground::get_scroll_base_scale() const { - return base_scale; } void ParallaxBackground::set_limit_begin(const Point2 &p_ofs) { - limit_begin = p_ofs; _update_scroll(); } Point2 ParallaxBackground::get_limit_begin() const { - return limit_begin; } void ParallaxBackground::set_limit_end(const Point2 &p_ofs) { - limit_end = p_ofs; _update_scroll(); } Point2 ParallaxBackground::get_limit_end() const { - return limit_end; } void ParallaxBackground::set_ignore_camera_zoom(bool ignore) { - ignore_camera_zoom = ignore; } bool ParallaxBackground::is_ignore_camera_zoom() { - return ignore_camera_zoom; } Vector2 ParallaxBackground::get_final_offset() const { - return final_offset; } void ParallaxBackground::_bind_methods() { - ClassDB::bind_method(D_METHOD("_camera_moved"), &ParallaxBackground::_camera_moved); ClassDB::bind_method(D_METHOD("set_scroll_offset", "ofs"), &ParallaxBackground::set_scroll_offset); ClassDB::bind_method(D_METHOD("get_scroll_offset"), &ParallaxBackground::get_scroll_offset); @@ -205,10 +185,5 @@ void ParallaxBackground::_bind_methods() { } ParallaxBackground::ParallaxBackground() { - - scale = 1.0; set_layer(-100); //behind all by default - - base_scale = Vector2(1, 1); - ignore_camera_zoom = false; } diff --git a/scene/2d/parallax_background.h b/scene/2d/parallax_background.h index 25ccd910d1..3745c5b587 100644 --- a/scene/2d/parallax_background.h +++ b/scene/2d/parallax_background.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,24 +31,21 @@ #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 { - GDCLASS(ParallaxBackground, CanvasLayer); Point2 offset; - float scale; + real_t scale = 1.0; Point2 base_offset; - Point2 base_scale; + Point2 base_scale = Vector2(1, 1); Point2 screen_offset; String group_name; Point2 limit_begin; Point2 limit_end; Point2 final_offset; - bool ignore_camera_zoom; + bool ignore_camera_zoom = false; void _update_scroll(); @@ -62,8 +59,8 @@ public: void set_scroll_offset(const Point2 &p_ofs); Point2 get_scroll_offset() const; - void set_scroll_scale(float p_scale); - float get_scroll_scale() const; + void set_scroll_scale(real_t p_scale); + real_t get_scroll_scale() const; void set_scroll_base_offset(const Point2 &p_ofs); Point2 get_scroll_base_offset() const; diff --git a/scene/2d/parallax_layer.cpp b/scene/2d/parallax_layer.cpp index 181f0f158c..67e35cc7a3 100644 --- a/scene/2d/parallax_layer.cpp +++ b/scene/2d/parallax_layer.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,51 +30,45 @@ #include "parallax_layer.h" -#include "core/engine.h" #include "parallax_background.h" void ParallaxLayer::set_motion_scale(const Size2 &p_scale) { - motion_scale = p_scale; ParallaxBackground *pb = Object::cast_to<ParallaxBackground>(get_parent()); if (pb && is_inside_tree()) { Vector2 ofs = pb->get_final_offset(); - float scale = pb->get_scroll_scale(); + real_t scale = pb->get_scroll_scale(); set_base_offset_and_scale(ofs, scale, screen_offset); } } Size2 ParallaxLayer::get_motion_scale() const { - return motion_scale; } void ParallaxLayer::set_motion_offset(const Size2 &p_offset) { - motion_offset = p_offset; ParallaxBackground *pb = Object::cast_to<ParallaxBackground>(get_parent()); if (pb && is_inside_tree()) { Vector2 ofs = pb->get_final_offset(); - float scale = pb->get_scroll_scale(); + real_t scale = pb->get_scroll_scale(); set_base_offset_and_scale(ofs, scale, screen_offset); } } Size2 ParallaxLayer::get_motion_offset() const { - return motion_offset; } void ParallaxLayer::_update_mirroring() { - - if (!is_inside_tree()) + if (!is_inside_tree()) { return; + } ParallaxBackground *pb = Object::cast_to<ParallaxBackground>(get_parent()); if (pb) { - RID c = pb->get_canvas(); RID ci = get_canvas_item(); Point2 mirrorScale = mirroring * get_scale(); @@ -83,32 +77,32 @@ void ParallaxLayer::_update_mirroring() { } void ParallaxLayer::set_mirroring(const Size2 &p_mirroring) { - mirroring = p_mirroring; - if (mirroring.x < 0) + if (mirroring.x < 0) { mirroring.x = 0; - if (mirroring.y < 0) + } + if (mirroring.y < 0) { mirroring.y = 0; + } _update_mirroring(); } Size2 ParallaxLayer::get_mirroring() const { - return mirroring; } void ParallaxLayer::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - orig_offset = get_position(); orig_scale = get_scale(); _update_mirroring(); } break; case NOTIFICATION_EXIT_TREE: { + if (Engine::get_singleton()->is_editor_hint()) { + break; + } set_position(orig_offset); set_scale(orig_scale); @@ -116,13 +110,15 @@ void ParallaxLayer::_notification(int p_what) { } } -void ParallaxLayer::set_base_offset_and_scale(const Point2 &p_offset, float p_scale, const Point2 &p_screen_offset) { +void ParallaxLayer::set_base_offset_and_scale(const Point2 &p_offset, real_t p_scale, const Point2 &p_screen_offset) { screen_offset = p_screen_offset; - if (!is_inside_tree()) + if (!is_inside_tree()) { return; - if (Engine::get_singleton()->is_editor_hint()) + } + if (Engine::get_singleton()->is_editor_hint()) { return; + } Point2 new_ofs = (screen_offset + (p_offset - screen_offset) * motion_scale) + motion_offset * p_scale + orig_offset * p_scale; @@ -142,17 +138,17 @@ void ParallaxLayer::set_base_offset_and_scale(const Point2 &p_offset, float p_sc _update_mirroring(); } -String ParallaxLayer::get_configuration_warning() const { +TypedArray<String> ParallaxLayer::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); if (!Object::cast_to<ParallaxBackground>(get_parent())) { - return TTR("ParallaxLayer node only works when set as child of a ParallaxBackground node."); + warnings.push_back(TTR("ParallaxLayer node only works when set as child of a ParallaxBackground node.")); } - return String(); + return warnings; } void ParallaxLayer::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_motion_scale", "scale"), &ParallaxLayer::set_motion_scale); ClassDB::bind_method(D_METHOD("get_motion_scale"), &ParallaxLayer::get_motion_scale); ClassDB::bind_method(D_METHOD("set_motion_offset", "offset"), &ParallaxLayer::set_motion_offset); @@ -167,5 +163,4 @@ void ParallaxLayer::_bind_methods() { } ParallaxLayer::ParallaxLayer() { - motion_scale = Size2(1, 1); } diff --git a/scene/2d/parallax_layer.h b/scene/2d/parallax_layer.h index ba59184649..cc2d2e096e 100644 --- a/scene/2d/parallax_layer.h +++ b/scene/2d/parallax_layer.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -34,12 +34,11 @@ #include "scene/2d/node_2d.h" class ParallaxLayer : public Node2D { - GDCLASS(ParallaxLayer, Node2D); Point2 orig_offset; Point2 orig_scale; - Size2 motion_scale; + Size2 motion_scale = Size2(1, 1); Vector2 motion_offset; Vector2 mirroring; void _update_mirroring(); @@ -60,9 +59,9 @@ public: void set_mirroring(const Size2 &p_mirroring); Size2 get_mirroring() const; - void set_base_offset_and_scale(const Point2 &p_offset, float p_scale, const Point2 &p_screen_offset); + void set_base_offset_and_scale(const Point2 &p_offset, real_t p_scale, const Point2 &p_screen_offset); - virtual String get_configuration_warning() const; + TypedArray<String> get_configuration_warnings() const override; ParallaxLayer(); }; diff --git a/scene/2d/path_2d.cpp b/scene/2d/path_2d.cpp index ed8481db4a..ed30e871d7 100644 --- a/scene/2d/path_2d.cpp +++ b/scene/2d/path_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,8 +30,7 @@ #include "path_2d.h" -#include "core/engine.h" -#include "scene/scene_string_names.h" +#include "core/math/geometry_2d.h" #ifdef TOOLS_ENABLED #include "editor/editor_scale.h" @@ -39,16 +38,14 @@ #ifdef TOOLS_ENABLED Rect2 Path2D::_edit_get_rect() const { - - if (!curve.is_valid() || curve->get_point_count() == 0) + if (!curve.is_valid() || curve->get_point_count() == 0) { return Rect2(0, 0, 0, 0); + } Rect2 aabb = Rect2(curve->get_point_position(0), Vector2(0, 0)); for (int i = 0; i < curve->get_point_count(); i++) { - for (int j = 0; j <= 8; j++) { - real_t frac = j / 8.0; Vector2 p = curve->interpolate(i, frac); aabb.expand_to(p); @@ -63,7 +60,6 @@ bool Path2D::_edit_use_rect() const { } bool Path2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { - if (curve.is_null()) { return false; } @@ -76,9 +72,10 @@ bool Path2D::_edit_is_selected_on_click(const Point2 &p_point, double p_toleranc real_t frac = j / 8.0; s[1] = curve->interpolate(i, frac); - Vector2 p = Geometry::get_closest_point_to_segment_2d(p_point, s); - if (p.distance_to(p_point) <= p_tolerance) + Vector2 p = Geometry2D::get_closest_point_to_segment(p_point, s); + if (p.distance_to(p_point) <= p_tolerance) { return true; + } s[0] = s[1]; } @@ -89,7 +86,6 @@ bool Path2D::_edit_is_selected_on_click(const Point2 &p_point, double p_toleranc #endif void Path2D::_notification(int p_what) { - if (p_what == NOTIFICATION_DRAW && curve.is_valid()) { //draw the curve!! @@ -98,18 +94,16 @@ void Path2D::_notification(int p_what) { } #ifdef TOOLS_ENABLED - const float line_width = 2 * EDSCALE; + const real_t line_width = 2 * EDSCALE; #else - const float line_width = 2; + const real_t line_width = 2; #endif - const Color color = Color(1.0, 1.0, 1.0, 1.0); + const Color color = Color(0.5, 0.6, 1.0, 0.7); for (int i = 0; i < curve->get_point_count(); i++) { - Vector2 prev_p = curve->get_point_position(i); for (int j = 1; j <= 8; j++) { - real_t frac = j / 8.0; Vector2 p = curve->interpolate(i, frac); draw_line(prev_p, p, color, line_width); @@ -132,7 +126,6 @@ void Path2D::_curve_changed() { } void Path2D::set_curve(const Ref<Curve2D> &p_curve) { - if (curve.is_valid()) { curve->disconnect("changed", callable_mp(this, &Path2D::_curve_changed)); } @@ -147,43 +140,36 @@ void Path2D::set_curve(const Ref<Curve2D> &p_curve) { } Ref<Curve2D> Path2D::get_curve() const { - return curve; } void Path2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_curve", "curve"), &Path2D::set_curve); ClassDB::bind_method(D_METHOD("get_curve"), &Path2D::get_curve); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve2D"), "set_curve", "get_curve"); -} - -Path2D::Path2D() { - - set_curve(Ref<Curve2D>(memnew(Curve2D))); //create one by default - set_self_modulate(Color(0.5, 0.6, 1.0, 0.7)); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT), "set_curve", "get_curve"); } ///////////////////////////////////////////////////////////////////////////////// void PathFollow2D::_update_transform() { - - if (!path) + if (!path) { return; + } Ref<Curve2D> c = path->get_curve(); - if (!c.is_valid()) + if (!c.is_valid()) { return; + } - float path_length = c->get_baked_length(); + real_t path_length = c->get_baked_length(); if (path_length == 0) { return; } Vector2 pos = c->interpolate_baked(offset, cubic); - if (rotate) { - float ahead = offset + lookahead; + if (rotates) { + real_t ahead = offset + lookahead; if (loop && ahead >= path_length) { // If our lookahead will loop, we need to check if the path is closed. @@ -212,7 +198,7 @@ void PathFollow2D::_update_transform() { tangent_to_curve = (ahead_pos - pos).normalized(); } - Vector2 normal_of_curve = -tangent_to_curve.tangent(); + Vector2 normal_of_curve = -tangent_to_curve.orthogonal(); pos += tangent_to_curve * h_offset; pos += normal_of_curve * v_offset; @@ -220,7 +206,6 @@ void PathFollow2D::_update_transform() { set_rotation(tangent_to_curve.angle()); } else { - pos.x += h_offset; pos.y += v_offset; } @@ -229,11 +214,8 @@ void PathFollow2D::_update_transform() { } void PathFollow2D::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - path = Object::cast_to<Path2D>(get_parent()); if (path) { _update_transform(); @@ -241,48 +223,43 @@ void PathFollow2D::_notification(int p_what) { } break; case NOTIFICATION_EXIT_TREE: { - path = nullptr; } break; } } void PathFollow2D::set_cubic_interpolation(bool p_enable) { - cubic = p_enable; } bool PathFollow2D::get_cubic_interpolation() const { - return cubic; } void PathFollow2D::_validate_property(PropertyInfo &property) const { - if (property.name == "offset") { - - float max = 10000; - if (path && path->get_curve().is_valid()) + real_t max = 10000.0; + if (path && path->get_curve().is_valid()) { max = path->get_curve()->get_baked_length(); + } property.hint_string = "0," + rtos(max) + ",0.01,or_lesser,or_greater"; } } -String PathFollow2D::get_configuration_warning() const { +TypedArray<String> PathFollow2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); - if (!is_visible_in_tree() || !is_inside_tree()) - return String(); - - if (!Object::cast_to<Path2D>(get_parent())) { - return TTR("PathFollow2D only works when set as a child of a Path2D node."); + if (is_visible_in_tree() && is_inside_tree()) { + if (!Object::cast_to<Path2D>(get_parent())) { + warnings.push_back(TTR("PathFollow2D only works when set as a child of a Path2D node.")); + } } - return String(); + return warnings; } void PathFollow2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_offset", "offset"), &PathFollow2D::set_offset); ClassDB::bind_method(D_METHOD("get_offset"), &PathFollow2D::get_offset); @@ -295,7 +272,7 @@ void PathFollow2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_unit_offset", "unit_offset"), &PathFollow2D::set_unit_offset); ClassDB::bind_method(D_METHOD("get_unit_offset"), &PathFollow2D::get_unit_offset); - ClassDB::bind_method(D_METHOD("set_rotate", "enable"), &PathFollow2D::set_rotate); + ClassDB::bind_method(D_METHOD("set_rotates", "enable"), &PathFollow2D::set_rotates); ClassDB::bind_method(D_METHOD("is_rotating"), &PathFollow2D::is_rotating); ClassDB::bind_method(D_METHOD("set_cubic_interpolation", "enable"), &PathFollow2D::set_cubic_interpolation); @@ -311,18 +288,17 @@ void PathFollow2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "unit_offset", PROPERTY_HINT_RANGE, "0,1,0.0001,or_lesser,or_greater", PROPERTY_USAGE_EDITOR), "set_unit_offset", "get_unit_offset"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "h_offset"), "set_h_offset", "get_h_offset"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "v_offset"), "set_v_offset", "get_v_offset"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "rotate"), "set_rotate", "is_rotating"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "rotates"), "set_rotates", "is_rotating"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cubic_interp"), "set_cubic_interpolation", "get_cubic_interpolation"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "has_loop"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lookahead", PROPERTY_HINT_RANGE, "0.001,1024.0,0.001"), "set_lookahead", "get_lookahead"); } -void PathFollow2D::set_offset(float p_offset) { - +void PathFollow2D::set_offset(real_t p_offset) { offset = p_offset; if (path) { if (path->get_curve().is_valid()) { - float path_length = path->get_curve()->get_baked_length(); + real_t path_length = path->get_curve()->get_baked_length(); if (loop) { offset = Math::fposmod(offset, path_length); @@ -336,92 +312,69 @@ void PathFollow2D::set_offset(float p_offset) { _update_transform(); } - _change_notify("offset"); - _change_notify("unit_offset"); } -void PathFollow2D::set_h_offset(float p_h_offset) { - +void PathFollow2D::set_h_offset(real_t p_h_offset) { h_offset = p_h_offset; - if (path) + if (path) { _update_transform(); + } } -float PathFollow2D::get_h_offset() const { - +real_t PathFollow2D::get_h_offset() const { return h_offset; } -void PathFollow2D::set_v_offset(float p_v_offset) { - +void PathFollow2D::set_v_offset(real_t p_v_offset) { v_offset = p_v_offset; - if (path) + if (path) { _update_transform(); + } } -float PathFollow2D::get_v_offset() const { - +real_t PathFollow2D::get_v_offset() const { return v_offset; } -float PathFollow2D::get_offset() const { - +real_t PathFollow2D::get_offset() const { return offset; } -void PathFollow2D::set_unit_offset(float p_unit_offset) { - - if (path && path->get_curve().is_valid() && path->get_curve()->get_baked_length()) +void PathFollow2D::set_unit_offset(real_t p_unit_offset) { + if (path && path->get_curve().is_valid() && path->get_curve()->get_baked_length()) { set_offset(p_unit_offset * path->get_curve()->get_baked_length()); + } } -float PathFollow2D::get_unit_offset() const { - - if (path && path->get_curve().is_valid() && path->get_curve()->get_baked_length()) +real_t PathFollow2D::get_unit_offset() const { + if (path && path->get_curve().is_valid() && path->get_curve()->get_baked_length()) { return get_offset() / path->get_curve()->get_baked_length(); - else + } else { return 0; + } } -void PathFollow2D::set_lookahead(float p_lookahead) { - +void PathFollow2D::set_lookahead(real_t p_lookahead) { lookahead = p_lookahead; } -float PathFollow2D::get_lookahead() const { - +real_t PathFollow2D::get_lookahead() const { return lookahead; } -void PathFollow2D::set_rotate(bool p_rotate) { - - rotate = p_rotate; +void PathFollow2D::set_rotates(bool p_rotates) { + rotates = p_rotates; _update_transform(); } bool PathFollow2D::is_rotating() const { - - return rotate; + return rotates; } void PathFollow2D::set_loop(bool p_loop) { - loop = p_loop; } bool PathFollow2D::has_loop() const { - return loop; } - -PathFollow2D::PathFollow2D() { - - offset = 0; - h_offset = 0; - v_offset = 0; - path = nullptr; - rotate = true; - cubic = true; - loop = true; - lookahead = 4; -} diff --git a/scene/2d/path_2d.h b/scene/2d/path_2d.h index 35cf8211f4..3b12f025fc 100644 --- a/scene/2d/path_2d.h +++ b/scene/2d/path_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -35,7 +35,6 @@ #include "scene/resources/curve.h" class Path2D : public Node2D { - GDCLASS(Path2D, Node2D); Ref<Curve2D> curve; @@ -48,68 +47,67 @@ protected: public: #ifdef TOOLS_ENABLED - virtual Rect2 _edit_get_rect() const; - virtual bool _edit_use_rect() const; - virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const; + virtual Rect2 _edit_get_rect() const override; + virtual bool _edit_use_rect() const override; + virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override; #endif void set_curve(const Ref<Curve2D> &p_curve); Ref<Curve2D> get_curve() const; - Path2D(); + Path2D() {} }; class PathFollow2D : public Node2D { - GDCLASS(PathFollow2D, Node2D); public: private: - Path2D *path; - real_t offset; - real_t h_offset; - real_t v_offset; - real_t lookahead; - bool cubic; - bool loop; - bool rotate; + Path2D *path = nullptr; + real_t offset = 0.0; + real_t h_offset = 0.0; + real_t v_offset = 0.0; + real_t lookahead = 4.0; + bool cubic = true; + bool loop = true; + bool rotates = true; void _update_transform(); protected: - virtual void _validate_property(PropertyInfo &property) const; + virtual void _validate_property(PropertyInfo &property) const override; void _notification(int p_what); static void _bind_methods(); public: - void set_offset(float p_offset); - float get_offset() const; + void set_offset(real_t p_offset); + real_t get_offset() const; - void set_h_offset(float p_h_offset); - float get_h_offset() const; + void set_h_offset(real_t p_h_offset); + real_t get_h_offset() const; - void set_v_offset(float p_v_offset); - float get_v_offset() const; + void set_v_offset(real_t p_v_offset); + real_t get_v_offset() const; - void set_unit_offset(float p_unit_offset); - float get_unit_offset() const; + void set_unit_offset(real_t p_unit_offset); + real_t get_unit_offset() const; - void set_lookahead(float p_lookahead); - float get_lookahead() const; + void set_lookahead(real_t p_lookahead); + real_t get_lookahead() const; void set_loop(bool p_loop); bool has_loop() const; - void set_rotate(bool p_rotate); + void set_rotates(bool p_rotates); bool is_rotating() const; void set_cubic_interpolation(bool p_enable); bool get_cubic_interpolation() const; - String get_configuration_warning() const; + TypedArray<String> get_configuration_warnings() const override; - PathFollow2D(); + PathFollow2D() {} }; #endif // PATH_2D_H diff --git a/scene/2d/physical_bone_2d.cpp b/scene/2d/physical_bone_2d.cpp new file mode 100644 index 0000000000..d547914e16 --- /dev/null +++ b/scene/2d/physical_bone_2d.cpp @@ -0,0 +1,307 @@ +/*************************************************************************/ +/* physical_bone_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 "physical_bone_2d.h" + +void PhysicalBone2D::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + // Position the RigidBody in the correct position. + if (follow_bone_when_simulating) { + _position_at_bone2d(); + } + + // Keep the child joint in the correct position. + if (child_joint && auto_configure_joint) { + child_joint->set_global_position(get_global_position()); + } + } break; + + case NOTIFICATION_READY: { + _find_skeleton_parent(); + _find_joint_child(); + + // Configure joint. + if (child_joint && auto_configure_joint) { + _auto_configure_joint(); + } + + // Simulate physics if set. + if (simulate_physics) { + _start_physics_simulation(); + } else { + _stop_physics_simulation(); + } + + set_physics_process_internal(true); + } break; + } +} + +void PhysicalBone2D::_position_at_bone2d() { + // Reset to Bone2D position + if (parent_skeleton) { + Bone2D *bone_to_use = parent_skeleton->get_bone(bone2d_index); + ERR_FAIL_COND_MSG(bone_to_use == nullptr, "It's not possible to position the bone with ID: " + itos(bone2d_index)); + set_global_transform(bone_to_use->get_global_transform()); + } +} + +void PhysicalBone2D::_find_skeleton_parent() { + Node *current_parent = get_parent(); + + while (current_parent != nullptr) { + Skeleton2D *potential_skeleton = Object::cast_to<Skeleton2D>(current_parent); + if (potential_skeleton) { + parent_skeleton = potential_skeleton; + break; + } else { + PhysicalBone2D *potential_parent_bone = Object::cast_to<PhysicalBone2D>(current_parent); + if (potential_parent_bone) { + current_parent = potential_parent_bone->get_parent(); + } else { + current_parent = nullptr; + } + } + } +} + +void PhysicalBone2D::_find_joint_child() { + for (int i = 0; i < get_child_count(); i++) { + Node *child_node = get_child(i); + Joint2D *potential_joint = Object::cast_to<Joint2D>(child_node); + if (potential_joint) { + child_joint = potential_joint; + break; + } + } +} + +TypedArray<String> PhysicalBone2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); + + if (!parent_skeleton) { + warnings.push_back(TTR("A PhysicalBone2D only works with a Skeleton2D or another PhysicalBone2D as a parent node!")); + } + if (parent_skeleton && bone2d_index <= -1) { + warnings.push_back(TTR("A PhysicalBone2D needs to be assigned to a Bone2D node in order to function! Please set a Bone2D node in the inspector.")); + } + if (!child_joint) { + PhysicalBone2D *parent_bone = Object::cast_to<PhysicalBone2D>(get_parent()); + if (parent_bone) { + warnings.push_back(TTR("A PhysicalBone2D node should have a Joint2D-based child node to keep bones connected! Please add a Joint2D-based node as a child to this node!")); + } + } + + return warnings; +} + +void PhysicalBone2D::_auto_configure_joint() { + if (!auto_configure_joint) { + return; + } + + if (child_joint) { + // Node A = parent | Node B = this node + Node *parent_node = get_parent(); + PhysicalBone2D *potential_parent_bone = Object::cast_to<PhysicalBone2D>(parent_node); + + if (potential_parent_bone) { + child_joint->set_node_a(child_joint->get_path_to(potential_parent_bone)); + child_joint->set_node_b(child_joint->get_path_to(this)); + } else { + WARN_PRINT("Cannot setup joint without a parent PhysicalBone2D node."); + } + + // Place the child joint at this node's position. + child_joint->set_global_position(get_global_position()); + } +} + +void PhysicalBone2D::_start_physics_simulation() { + if (_internal_simulate_physics) { + return; + } + + // Reset to Bone2D position + _position_at_bone2d(); + + // Apply the layers and masks + PhysicsServer2D::get_singleton()->body_set_collision_layer(get_rid(), get_collision_layer()); + PhysicsServer2D::get_singleton()->body_set_collision_mask(get_rid(), get_collision_mask()); + + // Apply the correct mode + RigidBody2D::Mode rigid_mode = get_mode(); + if (rigid_mode == RigidBody2D::MODE_STATIC) { + set_body_mode(PhysicsServer2D::BODY_MODE_STATIC); + } else if (rigid_mode == RigidBody2D::MODE_DYNAMIC) { + set_body_mode(PhysicsServer2D::BODY_MODE_DYNAMIC); + } else if (rigid_mode == RigidBody2D::MODE_KINEMATIC) { + set_body_mode(PhysicsServer2D::BODY_MODE_KINEMATIC); + } else if (rigid_mode == RigidBody2D::MODE_DYNAMIC_LOCKED) { + set_body_mode(PhysicsServer2D::BODY_MODE_DYNAMIC_LOCKED); + } else { + // Default to Dynamic. + set_body_mode(PhysicsServer2D::BODY_MODE_DYNAMIC); + } + + _internal_simulate_physics = true; + set_physics_process_internal(true); +} + +void PhysicalBone2D::_stop_physics_simulation() { + if (_internal_simulate_physics) { + _internal_simulate_physics = false; + + // Reset to Bone2D position + _position_at_bone2d(); + + set_physics_process_internal(false); + PhysicsServer2D::get_singleton()->body_set_collision_layer(get_rid(), 0); + PhysicsServer2D::get_singleton()->body_set_collision_mask(get_rid(), 0); + PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BodyMode::BODY_MODE_STATIC); + } +} + +Joint2D *PhysicalBone2D::get_joint() const { + return child_joint; +} + +bool PhysicalBone2D::get_auto_configure_joint() const { + return auto_configure_joint; +} + +void PhysicalBone2D::set_auto_configure_joint(bool p_auto_configure) { + auto_configure_joint = p_auto_configure; + _auto_configure_joint(); +} + +void PhysicalBone2D::set_simulate_physics(bool p_simulate) { + if (p_simulate == simulate_physics) { + return; + } + simulate_physics = p_simulate; + + if (simulate_physics) { + _start_physics_simulation(); + } else { + _stop_physics_simulation(); + } +} + +bool PhysicalBone2D::get_simulate_physics() const { + return simulate_physics; +} + +bool PhysicalBone2D::is_simulating_physics() const { + return _internal_simulate_physics; +} + +void PhysicalBone2D::set_bone2d_nodepath(const NodePath &p_nodepath) { + bone2d_nodepath = p_nodepath; + notify_property_list_changed(); +} + +NodePath PhysicalBone2D::get_bone2d_nodepath() const { + return bone2d_nodepath; +} + +void PhysicalBone2D::set_bone2d_index(int p_bone_idx) { + ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); + + if (!is_inside_tree()) { + bone2d_index = p_bone_idx; + return; + } + + if (parent_skeleton) { + ERR_FAIL_INDEX_MSG(p_bone_idx, parent_skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); + bone2d_index = p_bone_idx; + + bone2d_nodepath = get_path_to(parent_skeleton->get_bone(bone2d_index)); + } else { + WARN_PRINT("Cannot verify bone index..."); + bone2d_index = p_bone_idx; + } + + notify_property_list_changed(); +} + +int PhysicalBone2D::get_bone2d_index() const { + return bone2d_index; +} + +void PhysicalBone2D::set_follow_bone_when_simulating(bool p_follow_bone) { + follow_bone_when_simulating = p_follow_bone; + + if (_internal_simulate_physics) { + _stop_physics_simulation(); + _start_physics_simulation(); + } +} + +bool PhysicalBone2D::get_follow_bone_when_simulating() const { + return follow_bone_when_simulating; +} + +void PhysicalBone2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_joint"), &PhysicalBone2D::get_joint); + ClassDB::bind_method(D_METHOD("get_auto_configure_joint"), &PhysicalBone2D::get_auto_configure_joint); + ClassDB::bind_method(D_METHOD("set_auto_configure_joint", "auto_configure_joint"), &PhysicalBone2D::set_auto_configure_joint); + + ClassDB::bind_method(D_METHOD("set_simulate_physics", "simulate_physics"), &PhysicalBone2D::set_simulate_physics); + ClassDB::bind_method(D_METHOD("get_simulate_physics"), &PhysicalBone2D::get_simulate_physics); + ClassDB::bind_method(D_METHOD("is_simulating_physics"), &PhysicalBone2D::is_simulating_physics); + + ClassDB::bind_method(D_METHOD("set_bone2d_nodepath", "nodepath"), &PhysicalBone2D::set_bone2d_nodepath); + ClassDB::bind_method(D_METHOD("get_bone2d_nodepath"), &PhysicalBone2D::get_bone2d_nodepath); + ClassDB::bind_method(D_METHOD("set_bone2d_index", "bone_index"), &PhysicalBone2D::set_bone2d_index); + ClassDB::bind_method(D_METHOD("get_bone2d_index"), &PhysicalBone2D::get_bone2d_index); + ClassDB::bind_method(D_METHOD("set_follow_bone_when_simulating", "follow_bone"), &PhysicalBone2D::set_follow_bone_when_simulating); + ClassDB::bind_method(D_METHOD("get_follow_bone_when_simulating"), &PhysicalBone2D::get_follow_bone_when_simulating); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "bone2d_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D"), "set_bone2d_nodepath", "get_bone2d_nodepath"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "bone2d_index", PROPERTY_HINT_RANGE, "-1, 1000, 1"), "set_bone2d_index", "get_bone2d_index"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_configure_joint"), "set_auto_configure_joint", "get_auto_configure_joint"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "simulate_physics"), "set_simulate_physics", "get_simulate_physics"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "follow_bone_when_simulating"), "set_follow_bone_when_simulating", "get_follow_bone_when_simulating"); +} + +PhysicalBone2D::PhysicalBone2D() { + // Stop the RigidBody from executing its force integration. + PhysicsServer2D::get_singleton()->body_set_collision_layer(get_rid(), 0); + PhysicsServer2D::get_singleton()->body_set_collision_mask(get_rid(), 0); + PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BodyMode::BODY_MODE_STATIC); + + child_joint = nullptr; +} + +PhysicalBone2D::~PhysicalBone2D() { +} diff --git a/scene/2d/navigation_2d.h b/scene/2d/physical_bone_2d.h index 1da13fc78a..46a2772bad 100644 --- a/scene/2d/navigation_2d.h +++ b/scene/2d/physical_bone_2d.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* navigation_2d.h */ +/* physical_bone_2d.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,45 +28,61 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef NAVIGATION_2D_H -#define NAVIGATION_2D_H +#ifndef PHYSICAL_BONE_2D_H +#define PHYSICAL_BONE_2D_H -#include "scene/2d/navigation_region_2d.h" -#include "scene/2d/node_2d.h" +#include "scene/2d/joints_2d.h" +#include "scene/2d/physics_body_2d.h" -class Navigation2D : public Node2D { +#include "scene/2d/skeleton_2d.h" - GDCLASS(Navigation2D, Node2D); - - RID map; - real_t cell_size; - real_t edge_connection_margin; +class PhysicalBone2D : public RigidBody2D { + GDCLASS(PhysicalBone2D, RigidBody2D); protected: - static void _bind_methods(); void _notification(int p_what); + static void _bind_methods(); + +private: + Skeleton2D *parent_skeleton = nullptr; + int bone2d_index = -1; + NodePath bone2d_nodepath; + bool follow_bone_when_simulating = false; + + Joint2D *child_joint; + bool auto_configure_joint = true; + + bool simulate_physics = false; + bool _internal_simulate_physics = false; + + void _find_skeleton_parent(); + void _find_joint_child(); + void _auto_configure_joint(); + + void _start_physics_simulation(); + void _stop_physics_simulation(); + void _position_at_bone2d(); public: - RID get_rid() const { - return map; - } - - void set_cell_size(float p_cell_size); - float get_cell_size() const { - return cell_size; - } - - void set_edge_connection_margin(float p_edge_connection_margin); - float get_edge_connection_margin() const { - return edge_connection_margin; - } - - Vector<Vector2> get_simple_path(const Vector2 &p_start, const Vector2 &p_end, bool p_optimize = true) const; - Vector2 get_closest_point(const Vector2 &p_point) const; - RID get_closest_point_owner(const Vector2 &p_point) const; - - Navigation2D(); - ~Navigation2D(); + Joint2D *get_joint() const; + bool get_auto_configure_joint() const; + void set_auto_configure_joint(bool p_auto_configure); + + void set_simulate_physics(bool p_simulate); + bool get_simulate_physics() const; + bool is_simulating_physics() const; + + void set_bone2d_nodepath(const NodePath &p_nodepath); + NodePath get_bone2d_nodepath() const; + void set_bone2d_index(int p_bone_idx); + int get_bone2d_index() const; + void set_follow_bone_when_simulating(bool p_follow); + bool get_follow_bone_when_simulating() const; + + TypedArray<String> get_configuration_warnings() const override; + + PhysicalBone2D(); + ~PhysicalBone2D(); }; -#endif // NAVIGATION_2D_H +#endif // PHYSICAL_BONE_2D_H diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp index 21dc9537ec..30f012c7aa 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,120 +31,115 @@ #include "physics_body_2d.h" #include "core/core_string_names.h" -#include "core/engine.h" -#include "core/list.h" -#include "core/math/math_funcs.h" -#include "core/method_bind_ext.gen.inc" -#include "core/object.h" -#include "core/rid.h" #include "scene/scene_string_names.h" -void PhysicsBody2D::_notification(int p_what) { -} - -void PhysicsBody2D::_set_layers(uint32_t p_mask) { - - set_collision_layer(p_mask); - set_collision_mask(p_mask); -} - -uint32_t PhysicsBody2D::_get_layers() const { - - return get_collision_layer(); -} - void PhysicsBody2D::_bind_methods() { - - ClassDB::bind_method(D_METHOD("set_collision_layer", "layer"), &PhysicsBody2D::set_collision_layer); - ClassDB::bind_method(D_METHOD("get_collision_layer"), &PhysicsBody2D::get_collision_layer); - ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &PhysicsBody2D::set_collision_mask); - ClassDB::bind_method(D_METHOD("get_collision_mask"), &PhysicsBody2D::get_collision_mask); - - ClassDB::bind_method(D_METHOD("set_collision_mask_bit", "bit", "value"), &PhysicsBody2D::set_collision_mask_bit); - ClassDB::bind_method(D_METHOD("get_collision_mask_bit", "bit"), &PhysicsBody2D::get_collision_mask_bit); - - ClassDB::bind_method(D_METHOD("set_collision_layer_bit", "bit", "value"), &PhysicsBody2D::set_collision_layer_bit); - ClassDB::bind_method(D_METHOD("get_collision_layer_bit", "bit"), &PhysicsBody2D::get_collision_layer_bit); - - ClassDB::bind_method(D_METHOD("_set_layers", "mask"), &PhysicsBody2D::_set_layers); - ClassDB::bind_method(D_METHOD("_get_layers"), &PhysicsBody2D::_get_layers); + 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); ClassDB::bind_method(D_METHOD("remove_collision_exception_with", "body"), &PhysicsBody2D::remove_collision_exception_with); - ADD_PROPERTY(PropertyInfo(Variant::INT, "layers", PROPERTY_HINT_LAYERS_2D_PHYSICS, "", 0), "_set_layers", "_get_layers"); //for backwards compat - - ADD_GROUP("Collision", "collision_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_layer", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_layer", "get_collision_layer"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_mask", "get_collision_mask"); } -void PhysicsBody2D::set_collision_layer(uint32_t p_layer) { - - collision_layer = p_layer; - PhysicsServer2D::get_singleton()->body_set_collision_layer(get_rid(), p_layer); +PhysicsBody2D::PhysicsBody2D(PhysicsServer2D::BodyMode p_mode) : + CollisionObject2D(PhysicsServer2D::get_singleton()->body_create(), false) { + set_body_mode(p_mode); + set_pickable(false); } -uint32_t PhysicsBody2D::get_collision_layer() const { - - return collision_layer; +PhysicsBody2D::~PhysicsBody2D() { + if (motion_cache.is_valid()) { + motion_cache->owner = nullptr; + } } -void PhysicsBody2D::set_collision_mask(uint32_t p_mask) { +Ref<KinematicCollision2D> PhysicsBody2D::_move(const Vector2 &p_motion, bool p_test_only, real_t p_margin) { + PhysicsServer2D::MotionResult result; - collision_mask = p_mask; - PhysicsServer2D::get_singleton()->body_set_collision_mask(get_rid(), p_mask); -} + if (move_and_collide(p_motion, result, p_margin, p_test_only)) { + if (motion_cache.is_null()) { + motion_cache.instantiate(); + motion_cache->owner = this; + } -uint32_t PhysicsBody2D::get_collision_mask() const { + motion_cache->result = result; + return motion_cache; + } - return collision_mask; + return Ref<KinematicCollision2D>(); } -void PhysicsBody2D::set_collision_mask_bit(int p_bit, bool p_value) { +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_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. + if (p_cancel_sliding) { + real_t motion_length = p_motion.length(); + real_t precision = 0.001; + + 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); + + if (r_result.collision_depth > (real_t)p_margin + precision) { + p_cancel_sliding = false; + } + } - uint32_t mask = get_collision_mask(); - if (p_value) - mask |= 1 << p_bit; - else - mask &= ~(1 << p_bit); - set_collision_mask(mask); -} -bool PhysicsBody2D::get_collision_mask_bit(int p_bit) const { + 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; + } - return get_collision_mask() & (1 << p_bit); -} + // Check depth of recovery. + 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, + // 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.travel = motion_normal * projected_length; + r_result.remainder = p_motion - r_result.travel; + } + } + } -void PhysicsBody2D::set_collision_layer_bit(int p_bit, bool p_value) { + if (!p_test_only) { + gt.elements[2] += r_result.travel; + set_global_transform(gt); + } - uint32_t collision_layer = get_collision_layer(); - if (p_value) - collision_layer |= 1 << p_bit; - else - collision_layer &= ~(1 << p_bit); - set_collision_layer(collision_layer); + return colliding; } -bool PhysicsBody2D::get_collision_layer_bit(int p_bit) const { - - return get_collision_layer() & (1 << p_bit); -} +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); -PhysicsBody2D::PhysicsBody2D(PhysicsServer2D::BodyMode p_mode) : - CollisionObject2D(PhysicsServer2D::get_singleton()->body_create(), false) { + PhysicsServer2D::MotionResult *r = nullptr; + if (r_collision.is_valid()) { + // Needs const_cast because method bindings don't support non-const Ref. + r = const_cast<PhysicsServer2D::MotionResult *>(&r_collision->result); + } - PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), p_mode); - collision_layer = 1; - collision_mask = 1; - set_pickable(false); + return PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), p_from, p_motion, p_margin, r); } -Array PhysicsBody2D::get_collision_exceptions() { +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); @@ -154,7 +149,6 @@ Array PhysicsBody2D::get_collision_exceptions() { } void PhysicsBody2D::add_collision_exception_with(Node *p_node) { - ERR_FAIL_NULL(p_node); PhysicsBody2D *physics_body = Object::cast_to<PhysicsBody2D>(p_node); ERR_FAIL_COND_MSG(!physics_body, "Collision exception only works between two objects of PhysicsBody2D type."); @@ -162,7 +156,6 @@ void PhysicsBody2D::add_collision_exception_with(Node *p_node) { } void PhysicsBody2D::remove_collision_exception_with(Node *p_node) { - ERR_FAIL_NULL(p_node); PhysicsBody2D *physics_body = Object::cast_to<PhysicsBody2D>(p_node); ERR_FAIL_COND_MSG(!physics_body, "Collision exception only works between two objects of PhysicsBody2D type."); @@ -170,23 +163,22 @@ void PhysicsBody2D::remove_collision_exception_with(Node *p_node) { } void StaticBody2D::set_constant_linear_velocity(const Vector2 &p_vel) { - constant_linear_velocity = p_vel; + 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; + PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, constant_angular_velocity); } Vector2 StaticBody2D::get_constant_linear_velocity() const { - return constant_linear_velocity; } -real_t StaticBody2D::get_constant_angular_velocity() const { +real_t StaticBody2D::get_constant_angular_velocity() const { return constant_angular_velocity; } @@ -210,7 +202,6 @@ Ref<PhysicsMaterial> StaticBody2D::get_physics_material_override() const { } 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); @@ -219,18 +210,13 @@ void StaticBody2D::_bind_methods() { 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::OBJECT, "physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material_override", "get_physics_material_override"); -} - -StaticBody2D::StaticBody2D() : - PhysicsBody2D(PhysicsServer2D::BODY_MODE_STATIC) { - - constant_angular_velocity = 0; } -StaticBody2D::~StaticBody2D() { +StaticBody2D::StaticBody2D(PhysicsServer2D::BodyMode p_mode) : + PhysicsBody2D(p_mode) { } void StaticBody2D::_reload_physics_characteristics() { @@ -243,8 +229,87 @@ void StaticBody2D::_reload_physics_characteristics() { } } -void RigidBody2D::_body_enter_tree(ObjectID p_id) { +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 (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); +} + +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) { Object *obj = ObjectDB::get_instance(p_id); Node *node = Object::cast_to<Node>(obj); ERR_FAIL_COND(!node); @@ -260,15 +325,13 @@ void RigidBody2D::_body_enter_tree(ObjectID p_id) { emit_signal(SceneStringNames::get_singleton()->body_entered, node); for (int i = 0; i < E->get().shapes.size(); i++) { - - emit_signal(SceneStringNames::get_singleton()->body_shape_entered, p_id, node, E->get().shapes[i].body_shape, E->get().shapes[i].local_shape); + emit_signal(SceneStringNames::get_singleton()->body_shape_entered, E->get().rid, node, E->get().shapes[i].body_shape, E->get().shapes[i].local_shape); } contact_monitor->locked = false; } void RigidBody2D::_body_exit_tree(ObjectID p_id) { - Object *obj = ObjectDB::get_instance(p_id); Node *node = Object::cast_to<Node>(obj); ERR_FAIL_COND(!node); @@ -283,15 +346,13 @@ void RigidBody2D::_body_exit_tree(ObjectID p_id) { emit_signal(SceneStringNames::get_singleton()->body_exited, node); for (int i = 0; i < E->get().shapes.size(); i++) { - - emit_signal(SceneStringNames::get_singleton()->body_shape_exited, p_id, node, E->get().shapes[i].body_shape, E->get().shapes[i].local_shape); + emit_signal(SceneStringNames::get_singleton()->body_shape_exited, E->get().rid, node, E->get().shapes[i].body_shape, E->get().shapes[i].local_shape); } contact_monitor->locked = false; } -void RigidBody2D::_body_inout(int p_status, ObjectID p_instance, int p_body_shape, int p_local_shape) { - +void RigidBody2D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, int p_body_shape, int p_local_shape) { bool body_in = p_status == 1; ObjectID objid = p_instance; @@ -305,8 +366,8 @@ void RigidBody2D::_body_inout(int p_status, ObjectID p_instance, int p_body_shap if (body_in) { if (!E) { - E = contact_monitor->body_map.insert(objid, BodyState()); + E->get().rid = p_body; //E->get().rc=0; E->get().in_scene = node && node->is_inside_tree(); if (node) { @@ -320,107 +381,98 @@ void RigidBody2D::_body_inout(int p_status, ObjectID p_instance, int p_body_shap //E->get().rc++; } - if (node) + if (node) { E->get().shapes.insert(ShapePair(p_body_shape, p_local_shape)); + } if (E->get().in_scene) { - emit_signal(SceneStringNames::get_singleton()->body_shape_entered, objid, node, p_body_shape, p_local_shape); + emit_signal(SceneStringNames::get_singleton()->body_shape_entered, p_body, node, p_body_shape, p_local_shape); } } else { - //E->get().rc--; - if (node) + if (node) { E->get().shapes.erase(ShapePair(p_body_shape, p_local_shape)); + } bool in_scene = E->get().in_scene; - if (E->get().shapes.empty()) { - + if (E->get().shapes.is_empty()) { if (node) { node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &RigidBody2D::_body_enter_tree)); node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &RigidBody2D::_body_exit_tree)); - if (in_scene) + if (in_scene) { emit_signal(SceneStringNames::get_singleton()->body_exited, node); + } } contact_monitor->body_map.erase(E); } if (node && in_scene) { - emit_signal(SceneStringNames::get_singleton()->body_shape_exited, objid, node, p_body_shape, p_local_shape); + emit_signal(SceneStringNames::get_singleton()->body_shape_exited, p_body, node, p_body_shape, p_local_shape); } } } struct _RigidBody2DInOut { - + RID rid; ObjectID id; - int shape; - int local_shape; + int shape = 0; + int local_shape = 0; }; -bool RigidBody2D::_test_motion(const Vector2 &p_motion, bool p_infinite_inertia, float p_margin, const Ref<PhysicsTestMotionResult2D> &p_result) { - - PhysicsServer2D::MotionResult *r = nullptr; - if (p_result.is_valid()) - r = p_result->get_result_ptr(); - return PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), get_global_transform(), p_motion, p_infinite_inertia, p_margin, r); +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::_direct_state_changed(Object *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(p_state->get_transform()); + } -#ifdef DEBUG_ENABLED - state = Object::cast_to<PhysicsDirectBodyState2D>(p_state); -#else - state = (PhysicsDirectBodyState2D *)p_state; //trust it -#endif + linear_velocity = p_state->get_linear_velocity(); + angular_velocity = p_state->get_angular_velocity(); - set_block_transform_notify(true); // don't want notify (would feedback loop) - if (mode != MODE_KINEMATIC) - set_global_transform(state->get_transform()); - linear_velocity = state->get_linear_velocity(); - angular_velocity = state->get_angular_velocity(); - if (sleeping != state->is_sleeping()) { - sleeping = state->is_sleeping(); + 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) { - contact_monitor->locked = true; //untag all int rc = 0; for (Map<ObjectID, BodyState>::Element *E = contact_monitor->body_map.front(); E; E = E->next()) { - for (int i = 0; i < E->get().shapes.size(); i++) { - E->get().shapes[i].tagged = false; rc++; } } - _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++) { - - 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) { + toadd[toadd_count].rid = rid; toadd[toadd_count].local_shape = local_shape; toadd[toadd_count].id = obj; toadd[toadd_count].shape = shape; @@ -431,7 +483,7 @@ void RigidBody2D::_direct_state_changed(Object *p_state) { ShapePair sp(shape, local_shape); int idx = E->get().shapes.find(sp); if (idx == -1) { - + toadd[toadd_count].rid = rid; toadd[toadd_count].local_shape = local_shape; toadd[toadd_count].id = obj; toadd[toadd_count].shape = shape; @@ -445,11 +497,9 @@ void RigidBody2D::_direct_state_changed(Object *p_state) { //put the ones to remove for (Map<ObjectID, BodyState>::Element *E = contact_monitor->body_map.front(); E; E = E->next()) { - for (int i = 0; i < E->get().shapes.size(); i++) { - if (!E->get().shapes[i].tagged) { - + toremove[toremove_count].rid = E->get().rid; toremove[toremove_count].body_id = E->key(); toremove[toremove_count].pair = E->get().shapes[i]; toremove_count++; @@ -457,89 +507,106 @@ void RigidBody2D::_direct_state_changed(Object *p_state) { } } - //process remotions + //process removals for (int i = 0; i < toremove_count; i++) { - - _body_inout(0, toremove[i].body_id, toremove[i].pair.body_shape, toremove[i].pair.local_shape); + _body_inout(0, toremove[i].rid, toremove[i].body_id, toremove[i].pair.body_shape, toremove[i].pair.local_shape); } - //process aditions + //process additions for (int i = 0; i < toadd_count; i++) { - - _body_inout(1, toadd[i].id, toadd[i].shape, toadd[i].local_shape); + _body_inout(1, toadd[i].rid, toadd[i].id, toadd[i].shape, toadd[i].local_shape); } contact_monitor->locked = false; } - - state = nullptr; } void RigidBody2D::set_mode(Mode p_mode) { - mode = p_mode; switch (p_mode) { - - case MODE_RIGID: { - - PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BODY_MODE_RIGID); + case MODE_DYNAMIC: { + set_body_mode(PhysicsServer2D::BODY_MODE_DYNAMIC); } break; case MODE_STATIC: { - - PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BODY_MODE_STATIC); + set_body_mode(PhysicsServer2D::BODY_MODE_STATIC); } break; case MODE_KINEMATIC: { - - PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BODY_MODE_KINEMATIC); + set_body_mode(PhysicsServer2D::BODY_MODE_KINEMATIC); } break; - case MODE_CHARACTER: { - PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BODY_MODE_CHARACTER); + case MODE_DYNAMIC_LOCKED: { + set_body_mode(PhysicsServer2D::BODY_MODE_DYNAMIC_LOCKED); } break; } } RigidBody2D::Mode RigidBody2D::get_mode() const { - return mode; } void RigidBody2D::set_mass(real_t p_mass) { - ERR_FAIL_COND(p_mass <= 0); mass = p_mass; - _change_notify("mass"); - _change_notify("weight"); PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_MASS, mass); } -real_t RigidBody2D::get_mass() const { +real_t RigidBody2D::get_mass() const { return mass; } 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_weight(real_t p_weight) { +void RigidBody2D::set_center_of_mass_mode(CenterOfMassMode p_mode) { + if (center_of_mass_mode == p_mode) { + return; + } + + center_of_mass_mode = p_mode; - set_mass(p_weight / (real_t(GLOBAL_DEF("physics/2d/default_gravity", 98)) / 10)); + 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; } -real_t RigidBody2D::get_weight() const { +void RigidBody2D::set_center_of_mass(const Vector2 &p_center_of_mass) { + if (center_of_mass == p_center_of_mass) { + return; + } - return mass * (real_t(GLOBAL_DEF("physics/2d/default_gravity", 98)) / 10); + 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) { @@ -562,123 +629,96 @@ Ref<PhysicsMaterial> RigidBody2D::get_physics_material_override() const { } void RigidBody2D::set_gravity_scale(real_t p_gravity_scale) { - gravity_scale = p_gravity_scale; PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_GRAVITY_SCALE, gravity_scale); } -real_t RigidBody2D::get_gravity_scale() const { +real_t RigidBody2D::get_gravity_scale() const { return gravity_scale; } void RigidBody2D::set_linear_damp(real_t p_linear_damp) { - ERR_FAIL_COND(p_linear_damp < -1); linear_damp = p_linear_damp; PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_LINEAR_DAMP, linear_damp); } -real_t RigidBody2D::get_linear_damp() const { +real_t RigidBody2D::get_linear_damp() const { return linear_damp; } void RigidBody2D::set_angular_damp(real_t p_angular_damp) { - ERR_FAIL_COND(p_angular_damp < -1); angular_damp = p_angular_damp; PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_ANGULAR_DAMP, angular_damp); } -real_t RigidBody2D::get_angular_damp() const { +real_t RigidBody2D::get_angular_damp() const { return angular_damp; } 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 { - return linear_velocity; } 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 { +real_t RigidBody2D::get_angular_velocity() const { return angular_velocity; } void RigidBody2D::set_use_custom_integrator(bool p_enable) { - - if (custom_integrator == p_enable) + if (custom_integrator == p_enable) { return; + } custom_integrator = p_enable; PhysicsServer2D::get_singleton()->body_set_omit_force_integration(get_rid(), p_enable); } -bool RigidBody2D::is_using_custom_integrator() { +bool RigidBody2D::is_using_custom_integrator() { return custom_integrator; } void RigidBody2D::set_sleeping(bool p_sleeping) { - sleeping = p_sleeping; PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_SLEEPING, sleeping); } void RigidBody2D::set_can_sleep(bool p_active) { - can_sleep = p_active; PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_CAN_SLEEP, p_active); } bool RigidBody2D::is_able_to_sleep() const { - return can_sleep; } bool RigidBody2D::is_sleeping() const { - return sleeping; } void RigidBody2D::set_max_contacts_reported(int p_amount) { - max_contacts_reported = p_amount; PhysicsServer2D::get_singleton()->body_set_max_contacts_reported(get_rid(), p_amount); } int RigidBody2D::get_max_contacts_reported() const { - return max_contacts_reported; } @@ -686,32 +726,27 @@ void RigidBody2D::apply_central_impulse(const Vector2 &p_impulse) { PhysicsServer2D::get_singleton()->body_apply_central_impulse(get_rid(), p_impulse); } -void RigidBody2D::apply_impulse(const Vector2 &p_offset, const Vector2 &p_impulse) { - - PhysicsServer2D::get_singleton()->body_apply_impulse(get_rid(), p_offset, p_impulse); +void RigidBody2D::apply_impulse(const Vector2 &p_impulse, const Vector2 &p_position) { + PhysicsServer2D::get_singleton()->body_apply_impulse(get_rid(), p_impulse, p_position); } -void RigidBody2D::apply_torque_impulse(float p_torque) { +void RigidBody2D::apply_torque_impulse(real_t p_torque) { PhysicsServer2D::get_singleton()->body_apply_torque_impulse(get_rid(), p_torque); } void RigidBody2D::set_applied_force(const Vector2 &p_force) { - PhysicsServer2D::get_singleton()->body_set_applied_force(get_rid(), p_force); }; Vector2 RigidBody2D::get_applied_force() const { - return PhysicsServer2D::get_singleton()->body_get_applied_force(get_rid()); }; -void RigidBody2D::set_applied_torque(const float p_torque) { - +void RigidBody2D::set_applied_torque(const real_t p_torque) { PhysicsServer2D::get_singleton()->body_set_applied_torque(get_rid(), p_torque); }; -float RigidBody2D::get_applied_torque() const { - +real_t RigidBody2D::get_applied_torque() const { return PhysicsServer2D::get_singleton()->body_get_applied_torque(get_rid()); }; @@ -719,31 +754,27 @@ void RigidBody2D::add_central_force(const Vector2 &p_force) { PhysicsServer2D::get_singleton()->body_add_central_force(get_rid(), p_force); } -void RigidBody2D::add_force(const Vector2 &p_offset, const Vector2 &p_force) { - - PhysicsServer2D::get_singleton()->body_add_force(get_rid(), p_offset, p_force); +void RigidBody2D::add_force(const Vector2 &p_force, const Vector2 &p_position) { + PhysicsServer2D::get_singleton()->body_add_force(get_rid(), p_force, p_position); } -void RigidBody2D::add_torque(const float p_torque) { +void RigidBody2D::add_torque(const real_t p_torque) { PhysicsServer2D::get_singleton()->body_add_torque(get_rid(), p_torque); } void RigidBody2D::set_continuous_collision_detection_mode(CCDMode p_mode) { - ccd_mode = p_mode; PhysicsServer2D::get_singleton()->body_set_continuous_collision_detection_mode(get_rid(), PhysicsServer2D::CCDMode(p_mode)); } RigidBody2D::CCDMode RigidBody2D::get_continuous_collision_detection_mode() const { - return ccd_mode; } -Array RigidBody2D::get_colliding_bodies() const { - +TypedArray<Node2D> RigidBody2D::get_colliding_bodies() const { ERR_FAIL_COND_V(!contact_monitor, Array()); - Array ret; + TypedArray<Node2D> ret; ret.resize(contact_monitor->body_map.size()); int idx = 0; for (const Map<ObjectID, BodyState>::Element *E = contact_monitor->body_map.front(); E; E = E->next()) { @@ -759,16 +790,14 @@ Array RigidBody2D::get_colliding_bodies() const { } void RigidBody2D::set_contact_monitor(bool p_enabled) { - - if (p_enabled == is_contact_monitor_enabled()) + if (p_enabled == is_contact_monitor_enabled()) { return; + } if (!p_enabled) { - ERR_FAIL_COND_MSG(contact_monitor->locked, "Can't disable contact monitoring during in/out callback. Use call_deferred(\"set_contact_monitor\", false) instead."); for (Map<ObjectID, BodyState>::Element *E = contact_monitor->body_map.front(); E; E = E->next()) { - //clean up mess Object *obj = ObjectDB::get_instance(E->key()); Node *node = Object::cast_to<Node>(obj); @@ -782,53 +811,46 @@ void RigidBody2D::set_contact_monitor(bool p_enabled) { memdelete(contact_monitor); contact_monitor = nullptr; } else { - contact_monitor = memnew(ContactMonitor); contact_monitor->locked = false; } } bool RigidBody2D::is_contact_monitor_enabled() const { - return contact_monitor != nullptr; } void RigidBody2D::_notification(int p_what) { - #ifdef TOOLS_ENABLED - if (p_what == NOTIFICATION_ENTER_TREE) { - if (Engine::get_singleton()->is_editor_hint()) { - set_notify_local_transform(true); //used for warnings and only in editor - } - } + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + if (Engine::get_singleton()->is_editor_hint()) { + set_notify_local_transform(true); //used for warnings and only in editor + } + } break; - if (p_what == NOTIFICATION_LOCAL_TRANSFORM_CHANGED) { - if (Engine::get_singleton()->is_editor_hint()) { - update_configuration_warning(); - } + case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { + if (Engine::get_singleton()->is_editor_hint()) { + update_configuration_warnings(); + } + } break; } - #endif } -String RigidBody2D::get_configuration_warning() const { - +TypedArray<String> RigidBody2D::get_configuration_warnings() const { Transform2D t = get_transform(); - String warning = CollisionObject2D::get_configuration_warning(); + TypedArray<String> warnings = CollisionObject2D::get_configuration_warnings(); - if ((get_mode() == MODE_RIGID || get_mode() == MODE_CHARACTER) && (ABS(t.elements[0].length() - 1.0) > 0.05 || ABS(t.elements[1].length() - 1.0) > 0.05)) { - if (warning != String()) { - warning += "\n\n"; - } - warning += TTR("Size changes to RigidBody2D (in character or rigid modes) will be overridden by the physics engine when running.\nChange the size in children collision shapes instead."); + if ((get_mode() == MODE_DYNAMIC || get_mode() == MODE_DYNAMIC_LOCKED) && (ABS(t.elements[0].length() - 1.0) > 0.05 || ABS(t.elements[1].length() - 1.0) > 0.05)) { + warnings.push_back(TTR("Size changes to RigidBody2D (in dynamic modes) will be overridden by the physics engine when running.\nChange the size in children collision shapes instead.")); } - return warning; + return warnings; } void RigidBody2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_mode", "mode"), &RigidBody2D::set_mode); ClassDB::bind_method(D_METHOD("get_mode"), &RigidBody2D::get_mode); @@ -838,8 +860,11 @@ 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_weight", "weight"), &RigidBody2D::set_weight); - ClassDB::bind_method(D_METHOD("get_weight"), &RigidBody2D::get_weight); + 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); @@ -872,8 +897,8 @@ void RigidBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_continuous_collision_detection_mode"), &RigidBody2D::get_continuous_collision_detection_mode); ClassDB::bind_method(D_METHOD("set_axis_velocity", "axis_velocity"), &RigidBody2D::set_axis_velocity); - ClassDB::bind_method(D_METHOD("apply_central_impulse", "impulse"), &RigidBody2D::apply_central_impulse); - ClassDB::bind_method(D_METHOD("apply_impulse", "offset", "impulse"), &RigidBody2D::apply_impulse); + ClassDB::bind_method(D_METHOD("apply_central_impulse", "impulse"), &RigidBody2D::apply_central_impulse, Vector2()); + ClassDB::bind_method(D_METHOD("apply_impulse", "impulse", "position"), &RigidBody2D::apply_impulse, Vector2()); ClassDB::bind_method(D_METHOD("apply_torque_impulse", "torque"), &RigidBody2D::apply_torque_impulse); ClassDB::bind_method(D_METHOD("set_applied_force", "force"), &RigidBody2D::set_applied_force); @@ -883,7 +908,7 @@ void RigidBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_applied_torque"), &RigidBody2D::get_applied_torque); ClassDB::bind_method(D_METHOD("add_central_force", "force"), &RigidBody2D::add_central_force); - ClassDB::bind_method(D_METHOD("add_force", "offset", "force"), &RigidBody2D::add_force); + ClassDB::bind_method(D_METHOD("add_force", "force", "position"), &RigidBody2D::add_force, Vector2()); ClassDB::bind_method(D_METHOD("add_torque", "torque"), &RigidBody2D::add_torque); ClassDB::bind_method(D_METHOD("set_sleeping", "sleeping"), &RigidBody2D::set_sleeping); @@ -892,18 +917,16 @@ void RigidBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_can_sleep", "able_to_sleep"), &RigidBody2D::set_can_sleep); ClassDB::bind_method(D_METHOD("is_able_to_sleep"), &RigidBody2D::is_able_to_sleep); - ClassDB::bind_method(D_METHOD("test_motion", "motion", "infinite_inertia", "margin", "result"), &RigidBody2D::_test_motion, DEFVAL(true), DEFVAL(0.08), DEFVAL(Variant())); - - ClassDB::bind_method(D_METHOD("_direct_state_changed"), &RigidBody2D::_direct_state_changed); - 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, "Rigid,Static,Character,Kinematic"), "set_mode", "get_mode"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass", PROPERTY_HINT_EXP_RANGE, "0.01,65535,0.01"), "set_mass", "get_mass"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "inertia", PROPERTY_HINT_EXP_RANGE, "0.01,65535,0.01", 0), "set_inertia", "get_inertia"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "weight", PROPERTY_HINT_EXP_RANGE, "0.01,65535,0.01", PROPERTY_USAGE_EDITOR), "set_weight", "get_weight"); + 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,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,51 +945,42 @@ void RigidBody2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "applied_force"), "set_applied_force", "get_applied_force"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "applied_torque"), "set_applied_torque", "get_applied_torque"); - ADD_SIGNAL(MethodInfo("body_shape_entered", PropertyInfo(Variant::INT, "body_id"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::INT, "body_shape"), PropertyInfo(Variant::INT, "local_shape"))); - ADD_SIGNAL(MethodInfo("body_shape_exited", PropertyInfo(Variant::INT, "body_id"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::INT, "body_shape"), PropertyInfo(Variant::INT, "local_shape"))); + ADD_SIGNAL(MethodInfo("body_shape_entered", PropertyInfo(Variant::RID, "body_rid"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::INT, "body_shape"), PropertyInfo(Variant::INT, "local_shape"))); + ADD_SIGNAL(MethodInfo("body_shape_exited", PropertyInfo(Variant::RID, "body_rid"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::INT, "body_shape"), PropertyInfo(Variant::INT, "local_shape"))); ADD_SIGNAL(MethodInfo("body_entered", PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); ADD_SIGNAL(MethodInfo("body_exited", PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); ADD_SIGNAL(MethodInfo("sleeping_state_changed")); - BIND_ENUM_CONSTANT(MODE_RIGID); + BIND_ENUM_CONSTANT(MODE_DYNAMIC); BIND_ENUM_CONSTANT(MODE_STATIC); - BIND_ENUM_CONSTANT(MODE_CHARACTER); + 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); } -RigidBody2D::RigidBody2D() : - PhysicsBody2D(PhysicsServer2D::BODY_MODE_RIGID) { - - mode = MODE_RIGID; - - mass = 1; - - gravity_scale = 1; - linear_damp = -1; - angular_damp = -1; - - max_contacts_reported = 0; - state = nullptr; - - angular_velocity = 0; - sleeping = false; - ccd_mode = CCD_MODE_DISABLED; - - custom_integrator = false; - contact_monitor = nullptr; - can_sleep = true; +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; + } + } +} - PhysicsServer2D::get_singleton()->body_set_force_integration_callback(get_rid(), this, "_direct_state_changed"); +RigidBody2D::RigidBody2D() : + PhysicsBody2D(PhysicsServer2D::BODY_MODE_DYNAMIC) { + PhysicsServer2D::get_singleton()->body_set_state_sync_callback(get_rid(), this, _body_state_changed_callback); } RigidBody2D::~RigidBody2D() { - - if (contact_monitor) + if (contact_monitor) { memdelete(contact_monitor); + } } void RigidBody2D::_reload_physics_characteristics() { @@ -981,396 +995,608 @@ void RigidBody2D::_reload_physics_characteristics() { ////////////////////////// -Ref<KinematicCollision2D> KinematicBody2D::_move(const Vector2 &p_motion, bool p_infinite_inertia, bool p_exclude_raycast_shapes, bool p_test_only) { +// So, if you pass 45 as limit, avoid numerical precision errors when angle is 45. +#define FLOOR_ANGLE_THRESHOLD 0.01 - Collision col; +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(); - if (move_and_collide(p_motion, p_infinite_inertia, col, p_exclude_raycast_shapes, p_test_only)) { - if (motion_cache.is_null()) { - motion_cache.instance(); - motion_cache->owner = this; - } - - motion_cache->collision = col; + Vector2 current_platform_velocity = platform_velocity; - return motion_cache; + 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(); + } } - return Ref<KinematicCollision2D>(); -} - -bool KinematicBody2D::separate_raycast_shapes(bool p_infinite_inertia, Collision &r_collision) { + motion_results.clear(); - PhysicsServer2D::SeparationResult sep_res[8]; //max 8 rays - - Transform2D gt = get_global_transform(); + bool was_on_floor = on_floor; + on_floor = false; + on_ceiling = false; + on_wall = false; - Vector2 recover; - int hits = PhysicsServer2D::get_singleton()->body_test_ray_separation(get_rid(), gt, p_infinite_inertia, recover, sep_res, 8, margin); - int deepest = -1; - float deepest_depth; - for (int i = 0; i < hits; i++) { - if (deepest == -1 || sep_res[i].collision_depth > deepest_depth) { - deepest = i; - deepest_depth = sep_res[i].collision_depth; + if (!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); } } - gt.elements[2] += recover; - set_global_transform(gt); - - if (deepest != -1) { - r_collision.collider = sep_res[deepest].collider_id; - r_collision.collider_metadata = sep_res[deepest].collider_metadata; - r_collision.collider_shape = sep_res[deepest].collider_shape; - r_collision.collider_vel = sep_res[deepest].collider_velocity; - r_collision.collision = sep_res[deepest].collision_point; - r_collision.normal = sep_res[deepest].collision_normal; - r_collision.local_shape = sep_res[deepest].collision_local_shape; - r_collision.travel = recover; - r_collision.remainder = Vector2(); - - return true; + if (motion_mode == MOTION_MODE_GROUNDED) { + _move_and_slide_grounded(delta, was_on_floor, current_platform_velocity); } else { - return false; + _move_and_slide_free(delta); } -} -bool KinematicBody2D::move_and_collide(const Vector2 &p_motion, bool p_infinite_inertia, Collision &r_collision, bool p_exclude_raycast_shapes, bool p_test_only) { - - if (sync_to_physics) { - ERR_PRINT("Functions move_and_slide and move_and_collide do not work together with 'sync to physics' option. Please read the documentation."); - } - Transform2D gt = get_global_transform(); - PhysicsServer2D::MotionResult result; - bool colliding = PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_infinite_inertia, margin, &result, p_exclude_raycast_shapes); - - if (colliding) { - r_collision.collider_metadata = result.collider_metadata; - r_collision.collider_shape = result.collider_shape; - r_collision.collider_vel = result.collider_velocity; - r_collision.collision = result.collision_point; - r_collision.normal = result.collision_normal; - r_collision.collider = result.collider_id; - r_collision.collider_rid = result.collider; - r_collision.travel = result.motion; - r_collision.remainder = result.remainder; - r_collision.local_shape = result.collision_local_shape; + if (!on_floor && !on_wall) { + // Add last platform velocity when just left a moving platform. + linear_velocity += current_platform_velocity; } - if (!p_test_only) { - gt.elements[2] += result.motion; - set_global_transform(gt); - } - - return colliding; + return motion_results.size() > 0; } -//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_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 KinematicBody2D::move_and_slide(const Vector2 &p_linear_velocity, const Vector2 &p_up_direction, bool p_stop_on_slope, int p_max_slides, float p_floor_max_angle, bool p_infinite_inertia) { + Vector2 prev_floor_normal = floor_normal; + RID prev_platform_rid = platform_rid; + int prev_platform_layer = platform_layer; - Vector2 body_velocity = p_linear_velocity; - Vector2 body_velocity_normal = body_velocity.normalized(); + platform_rid = RID(); + floor_normal = Vector2(); + platform_velocity = Vector2(); - 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(); - } - } + // 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; - // 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 + body_velocity) * (Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time()); + for (int iteration = 0; iteration < max_slides; ++iteration) { + PhysicsServer2D::MotionResult result; - on_floor = false; - on_floor_body = RID(); - on_ceiling = false; - on_wall = false; - colliders.clear(); - floor_normal = Vector2(); - floor_velocity = Vector2(); + Vector2 prev_position = get_global_transform().elements[2]; - while (p_max_slides) { + bool collided = move_and_collide(motion, result, margin, false, !sliding_enabled); - Collision collision; - bool found_collision = false; + if (collided) { + motion_results.push_back(result); + _set_collision_direction(result); - for (int i = 0; i < 2; ++i) { - bool collided; - if (i == 0) { //collide - collided = move_and_collide(motion, p_infinite_inertia, collision); - if (!collided) { - motion = Vector2(); //clear because no collision happened and motion completed - } - } else { //separate raycasts (if any) - collided = separate_raycast_shapes(p_infinite_inertia, collision); - if (collided) { - collision.remainder = motion; //keep - collision.travel = Vector2(); + 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 { + gt.elements[2] -= result.travel; } + set_global_transform(gt); + linear_velocity = Vector2(); + motion = Vector2(); + break; } - if (collided) { - found_collision = true; - - colliders.push_back(collision); - motion = collision.remainder; + if (result.remainder.is_equal_approx(Vector2())) { + motion = Vector2(); + break; + } - if (p_up_direction == Vector2()) { - //all is a wall - on_wall = true; + // 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 { - if (Math::acos(collision.normal.dot(p_up_direction)) <= p_floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor - - on_floor = true; - floor_normal = collision.normal; - on_floor_body = collision.collider_rid; - floor_velocity = collision.collider_vel; - - if (p_stop_on_slope) { - if ((body_velocity_normal + p_up_direction).length() < 0.01 && collision.travel.length() < 1) { - Transform2D gt = get_global_transform(); - gt.elements[2] -= collision.travel.slide(p_up_direction); - set_global_transform(gt); - return Vector2(); - } - } - } else if (Math::acos(collision.normal.dot(-p_up_direction)) <= p_floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling - on_ceiling = true; + 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 { - on_wall = true; + // Avoid acceleration in slope when falling. + linear_velocity = up_direction * up_direction.dot(linear_velocity); } } - - motion = motion.slide(collision.normal); - body_velocity = body_velocity.slide(collision.normal); } + // 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); - if (!found_collision || motion == Vector2()) - break; + Vector2 motion_slide_norm = motion.slide(prev_floor_normal).normalized(); + motion = motion_slide_norm * (motion_slide_up.length()); + collided = true; + } - --p_max_slides; + can_apply_constant_speed = !can_apply_constant_speed && !sliding_enabled; + sliding_enabled = true; + first_slide = false; + + if (!collided || motion.is_equal_approx(Vector2())) { + break; + } } - return body_velocity; + _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); + } } -Vector2 KinematicBody2D::move_and_slide_with_snap(const Vector2 &p_linear_velocity, const Vector2 &p_snap, const Vector2 &p_up_direction, bool p_stop_on_slope, int p_max_slides, float p_floor_max_angle, bool p_infinite_inertia) { +void CharacterBody2D::_move_and_slide_free(double p_delta) { + Vector2 motion = linear_velocity * p_delta; - bool was_on_floor = on_floor; + platform_rid = RID(); + floor_normal = Vector2(); + platform_velocity = Vector2(); - Vector2 ret = move_and_slide(p_linear_velocity, p_up_direction, p_stop_on_slope, p_max_slides, p_floor_max_angle, p_infinite_inertia); - if (!was_on_floor || p_snap == Vector2()) { - return ret; + 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; + } } +} - Collision col; - Transform2D gt = get_global_transform(); +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; + } - if (move_and_collide(p_snap, p_infinite_inertia, col, false, true)) { + Transform2D gt = get_global_transform(); + PhysicsServer2D::MotionResult result; + if (move_and_collide(up_direction * -floor_snap_length, result, margin, true, false, true)) { bool apply = true; - if (p_up_direction != Vector2()) { - if (Math::acos(p_up_direction.normalized().dot(col.normal)) < p_floor_max_angle) { - on_floor = true; - floor_normal = col.normal; - on_floor_body = col.collider_rid; - floor_velocity = col.collider_vel; - if (p_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. - col.travel = p_up_direction * p_up_direction.dot(col.travel); + 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] += col.travel; + gt.elements[2] += result.travel; set_global_transform(gt); } } - - return ret; } -bool KinematicBody2D::is_on_floor() const { +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; + } - return on_floor; -} -bool KinematicBody2D::is_on_wall() const { + 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; + } + } - return on_wall; + return false; } -bool KinematicBody2D::is_on_ceiling() const { - return on_ceiling; +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 { + 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); + } + } } -Vector2 KinematicBody2D::get_floor_normal() const { - - return floor_normal; +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(); + } } -Vector2 KinematicBody2D::get_floor_velocity() const { +const Vector2 &CharacterBody2D::get_linear_velocity() const { + return linear_velocity; +} - return floor_velocity; +void CharacterBody2D::set_linear_velocity(const Vector2 &p_velocity) { + linear_velocity = p_velocity; } -bool KinematicBody2D::test_move(const Transform2D &p_from, const Vector2 &p_motion, bool p_infinite_inertia) { +bool CharacterBody2D::is_on_floor() const { + return on_floor; +} - ERR_FAIL_COND_V(!is_inside_tree(), false); +bool CharacterBody2D::is_on_floor_only() const { + return on_floor && !on_wall && !on_ceiling; +} - return PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), p_from, p_motion, p_infinite_inertia, margin); +bool CharacterBody2D::is_on_wall() const { + return on_wall; } -void KinematicBody2D::set_safe_margin(float p_margin) { +bool CharacterBody2D::is_on_wall_only() const { + return on_wall && !on_floor && !on_ceiling; +} - margin = p_margin; +bool CharacterBody2D::is_on_ceiling() const { + return on_ceiling; } -float KinematicBody2D::get_safe_margin() const { +bool CharacterBody2D::is_on_ceiling_only() const { + return on_ceiling && !on_floor && !on_wall; +} - return margin; +Vector2 CharacterBody2D::get_floor_normal() const { + return floor_normal; } -int KinematicBody2D::get_slide_count() const { +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)); +} - return colliders.size(); +Vector2 CharacterBody2D::get_platform_velocity() const { + return platform_velocity; } -KinematicBody2D::Collision KinematicBody2D::get_slide_collision(int p_bounce) const { - ERR_FAIL_INDEX_V(p_bounce, colliders.size(), Collision()); - return colliders[p_bounce]; +int CharacterBody2D::get_slide_collision_count() const { + return motion_results.size(); } -Ref<KinematicCollision2D> KinematicBody2D::_get_slide_collision(int p_bounce) { +PhysicsServer2D::MotionResult CharacterBody2D::get_slide_collision(int p_bounce) const { + ERR_FAIL_INDEX_V(p_bounce, motion_results.size(), PhysicsServer2D::MotionResult()); + return motion_results[p_bounce]; +} - ERR_FAIL_INDEX_V(p_bounce, colliders.size(), Ref<KinematicCollision2D>()); +Ref<KinematicCollision2D> CharacterBody2D::_get_slide_collision(int p_bounce) { + ERR_FAIL_INDEX_V(p_bounce, motion_results.size(), Ref<KinematicCollision2D>()); if (p_bounce >= slide_colliders.size()) { slide_colliders.resize(p_bounce + 1); } if (slide_colliders[p_bounce].is_null()) { - slide_colliders.write[p_bounce].instance(); + slide_colliders.write[p_bounce].instantiate(); slide_colliders.write[p_bounce]->owner = this; } - slide_colliders.write[p_bounce]->collision = colliders[p_bounce]; + slide_colliders.write[p_bounce]->result = motion_results[p_bounce]; return slide_colliders[p_bounce]; } -void KinematicBody2D::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(), this, "_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(), nullptr, ""); - set_only_update_transform_changes(false); - set_notify_local_transform(false); - } +real_t CharacterBody2D::get_safe_margin() const { + return margin; } -bool KinematicBody2D::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 KinematicBody2D::_direct_state_changed(Object *p_state) { +void CharacterBody2D::set_floor_stop_on_slope_enabled(bool p_enabled) { + floor_stop_on_slope = p_enabled; +} - if (!sync_to_physics) - return; +bool CharacterBody2D::is_floor_constant_speed_enabled() const { + return floor_constant_speed; +} - PhysicsDirectBodyState2D *state = Object::cast_to<PhysicsDirectBodyState2D>(p_state); +void CharacterBody2D::set_floor_constant_speed_enabled(bool p_enabled) { + floor_constant_speed = p_enabled; +} - last_valid_transform = state->get_transform(); - set_notify_local_transform(false); - set_global_transform(last_valid_transform); - set_notify_local_transform(true); +bool CharacterBody2D::is_floor_block_on_wall_enabled() const { + return floor_block_on_wall; } -void KinematicBody2D::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE) { - last_valid_transform = get_global_transform(); +void CharacterBody2D::set_floor_block_on_wall_enabled(bool p_enabled) { + floor_block_on_wall = p_enabled; +} - // Reset move_and_slide() data. - on_floor = false; - on_floor_body = RID(); - on_ceiling = false; - on_wall = false; - colliders.clear(); - floor_velocity = Vector2(); - } +bool CharacterBody2D::is_slide_on_ceiling_enabled() const { + return slide_on_ceiling; +} - if (p_what == 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); - } +void CharacterBody2D::set_slide_on_ceiling_enabled(bool p_enabled) { + slide_on_ceiling = p_enabled; } -void KinematicBody2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("move_and_collide", "rel_vec", "infinite_inertia", "exclude_raycast_shapes", "test_only"), &KinematicBody2D::_move, DEFVAL(true), DEFVAL(true), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("move_and_slide", "linear_velocity", "up_direction", "stop_on_slope", "max_slides", "floor_max_angle", "infinite_inertia"), &KinematicBody2D::move_and_slide, DEFVAL(Vector2(0, 0)), DEFVAL(false), DEFVAL(4), DEFVAL(Math::deg2rad((float)45)), DEFVAL(true)); - ClassDB::bind_method(D_METHOD("move_and_slide_with_snap", "linear_velocity", "snap", "up_direction", "stop_on_slope", "max_slides", "floor_max_angle", "infinite_inertia"), &KinematicBody2D::move_and_slide_with_snap, DEFVAL(Vector2(0, 0)), DEFVAL(false), DEFVAL(4), DEFVAL(Math::deg2rad((float)45)), DEFVAL(true)); +uint32_t CharacterBody2D::get_moving_platform_floor_layers() const { + return moving_platform_floor_layers; +} - ClassDB::bind_method(D_METHOD("test_move", "from", "rel_vec", "infinite_inertia"), &KinematicBody2D::test_move, DEFVAL(true)); +void CharacterBody2D::set_moving_platform_floor_layers(uint32_t p_exclude_layers) { + moving_platform_floor_layers = p_exclude_layers; +} - ClassDB::bind_method(D_METHOD("is_on_floor"), &KinematicBody2D::is_on_floor); - ClassDB::bind_method(D_METHOD("is_on_ceiling"), &KinematicBody2D::is_on_ceiling); - ClassDB::bind_method(D_METHOD("is_on_wall"), &KinematicBody2D::is_on_wall); - ClassDB::bind_method(D_METHOD("get_floor_normal"), &KinematicBody2D::get_floor_normal); - ClassDB::bind_method(D_METHOD("get_floor_velocity"), &KinematicBody2D::get_floor_velocity); +uint32_t CharacterBody2D::get_moving_platform_wall_layers() const { + return moving_platform_wall_layers; +} - ClassDB::bind_method(D_METHOD("set_safe_margin", "pixels"), &KinematicBody2D::set_safe_margin); - ClassDB::bind_method(D_METHOD("get_safe_margin"), &KinematicBody2D::get_safe_margin); +void CharacterBody2D::set_moving_platform_wall_layers(uint32_t p_exclude_layers) { + moving_platform_wall_layers = p_exclude_layers; +} - ClassDB::bind_method(D_METHOD("get_slide_count"), &KinematicBody2D::get_slide_count); - ClassDB::bind_method(D_METHOD("get_slide_collision", "slide_idx"), &KinematicBody2D::_get_slide_collision); +void CharacterBody2D::set_motion_mode(MotionMode p_mode) { + motion_mode = p_mode; +} - ClassDB::bind_method(D_METHOD("set_sync_to_physics", "enable"), &KinematicBody2D::set_sync_to_physics); - ClassDB::bind_method(D_METHOD("is_sync_to_physics_enabled"), &KinematicBody2D::is_sync_to_physics_enabled); +CharacterBody2D::MotionMode CharacterBody2D::get_motion_mode() const { + return motion_mode; +} - ClassDB::bind_method(D_METHOD("_direct_state_changed"), &KinematicBody2D::_direct_state_changed); +int CharacterBody2D::get_max_slides() const { + return max_slides; +} - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision/safe_margin", PROPERTY_HINT_RANGE, "0.001,256,0.001"), "set_safe_margin", "get_safe_margin"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "motion/sync_to_physics"), "set_sync_to_physics", "is_sync_to_physics_enabled"); +void CharacterBody2D::set_max_slides(int p_max_slides) { + ERR_FAIL_COND(p_max_slides < 1); + max_slides = p_max_slides; } -KinematicBody2D::KinematicBody2D() : - PhysicsBody2D(PhysicsServer2D::BODY_MODE_KINEMATIC) { +real_t CharacterBody2D::get_floor_max_angle() const { + return floor_max_angle; +} - margin = 0.08; +void CharacterBody2D::set_floor_max_angle(real_t p_radians) { + floor_max_angle = p_radians; +} - on_floor = false; - on_ceiling = false; - on_wall = false; - sync_to_physics = false; +real_t CharacterBody2D::get_floor_snap_length() { + return floor_snap_length; } -KinematicBody2D::~KinematicBody2D() { - if (motion_cache.is_valid()) { - motion_cache->owner = nullptr; + +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_free_mode_min_slide_angle(real_t p_radians) { + free_mode_min_slide_angle = p_radians; +} + +const Vector2 &CharacterBody2D::get_up_direction() const { + return up_direction; +} + +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: { + // Reset move_and_slide() data. + on_floor = false; + platform_rid = RID(); + on_ceiling = false; + on_wall = false; + motion_results.clear(); + platform_velocity = Vector2(); + } break; } +} + +void CharacterBody2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("move_and_slide"), &CharacterBody2D::move_and_slide); + + ClassDB::bind_method(D_METHOD("set_linear_velocity", "linear_velocity"), &CharacterBody2D::set_linear_velocity); + ClassDB::bind_method(D_METHOD("get_linear_velocity"), &CharacterBody2D::get_linear_velocity); + + 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_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_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_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); + + 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, "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_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() : + PhysicsBody2D(PhysicsServer2D::BODY_MODE_KINEMATIC) { +} + +CharacterBody2D::~CharacterBody2D() { for (int i = 0; i < slide_colliders.size(); i++) { if (slide_colliders[i].is_valid()) { slide_colliders.write[i]->owner = nullptr; @@ -1381,71 +1607,85 @@ KinematicBody2D::~KinematicBody2D() { //////////////////////// Vector2 KinematicCollision2D::get_position() const { - - return collision.collision; + return result.collision_point; } + Vector2 KinematicCollision2D::get_normal() const { - return collision.normal; + return result.collision_normal; } + Vector2 KinematicCollision2D::get_travel() const { - return collision.travel; + return result.travel; } + Vector2 KinematicCollision2D::get_remainder() const { - return collision.remainder; + 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; - uint32_t ownerid = owner->shape_find_owner(collision.local_shape); + if (!owner) { + return nullptr; + } + uint32_t ownerid = owner->shape_find_owner(result.collision_local_shape); return owner->shape_owner_get_owner(ownerid); } Object *KinematicCollision2D::get_collider() const { - - if (collision.collider.is_valid()) { - return ObjectDB::get_instance(collision.collider); + if (result.collider_id.is_valid()) { + return ObjectDB::get_instance(result.collider_id); } return nullptr; } + ObjectID KinematicCollision2D::get_collider_id() const { + return result.collider_id; +} - return collision.collider; +RID KinematicCollision2D::get_collider_rid() const { + return result.collider; } -Object *KinematicCollision2D::get_collider_shape() const { +Object *KinematicCollision2D::get_collider_shape() const { Object *collider = get_collider(); if (collider) { CollisionObject2D *obj2d = Object::cast_to<CollisionObject2D>(collider); if (obj2d) { - uint32_t ownerid = obj2d->shape_find_owner(collision.collider_shape); + uint32_t ownerid = obj2d->shape_find_owner(result.collider_shape); return obj2d->shape_owner_get_owner(ownerid); } } return nullptr; } -int KinematicCollision2D::get_collider_shape_index() const { - return collision.collider_shape; +int KinematicCollision2D::get_collider_shape_index() const { + return result.collider_shape; } -Vector2 KinematicCollision2D::get_collider_velocity() const { - return collision.collider_vel; +Vector2 KinematicCollision2D::get_collider_velocity() const { + return result.collider_velocity; } -Variant KinematicCollision2D::get_collider_metadata() const { +Variant KinematicCollision2D::get_collider_metadata() const { return Variant(); } void KinematicCollision2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_position"), &KinematicCollision2D::get_position); 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); + ClassDB::bind_method(D_METHOD("get_collider_rid"), &KinematicCollision2D::get_collider_rid); ClassDB::bind_method(D_METHOD("get_collider_shape"), &KinematicCollision2D::get_collider_shape); ClassDB::bind_method(D_METHOD("get_collider_shape_index"), &KinematicCollision2D::get_collider_shape_index); ClassDB::bind_method(D_METHOD("get_collider_velocity"), &KinematicCollision2D::get_collider_velocity); @@ -1458,15 +1698,9 @@ void KinematicCollision2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "local_shape"), "", "get_local_shape"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "collider"), "", "get_collider"); ADD_PROPERTY(PropertyInfo(Variant::INT, "collider_id"), "", "get_collider_id"); + ADD_PROPERTY(PropertyInfo(Variant::RID, "collider_rid"), "", "get_collider_rid"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "collider_shape"), "", "get_collider_shape"); ADD_PROPERTY(PropertyInfo(Variant::INT, "collider_shape_index"), "", "get_collider_shape_index"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "collider_velocity"), "", "get_collider_velocity"); ADD_PROPERTY(PropertyInfo(Variant::NIL, "collider_metadata", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT), "", "get_collider_metadata"); } - -KinematicCollision2D::KinematicCollision2D() { - - collision.collider_shape = 0; - collision.local_shape = 0; - owner = nullptr; -} diff --git a/scene/2d/physics_body_2d.h b/scene/2d/physics_body_2d.h index 0d92a820e3..1d6437a3ad 100644 --- a/scene/2d/physics_body_2d.h +++ b/scene/2d/physics_body_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,7 +31,7 @@ #ifndef PHYSICS_BODY_2D_H #define PHYSICS_BODY_2D_H -#include "core/vset.h" +#include "core/templates/vset.h" #include "scene/2d/collision_object_2d.h" #include "scene/resources/physics_material.h" #include "servers/physics_server_2d.h" @@ -39,47 +39,33 @@ class KinematicCollision2D; class PhysicsBody2D : public CollisionObject2D { - GDCLASS(PhysicsBody2D, CollisionObject2D); - uint32_t collision_layer; - uint32_t collision_mask; - - void _set_layers(uint32_t p_mask); - uint32_t _get_layers() const; - protected: - void _notification(int p_what); - PhysicsBody2D(PhysicsServer2D::BodyMode p_mode); - static void _bind_methods(); + PhysicsBody2D(PhysicsServer2D::BodyMode p_mode); -public: - void set_collision_layer(uint32_t p_layer); - uint32_t get_collision_layer() const; - - void set_collision_mask(uint32_t p_mask); - uint32_t get_collision_mask() const; + Ref<KinematicCollision2D> motion_cache; - void set_collision_mask_bit(int p_bit, bool p_value); - bool get_collision_mask_bit(int p_bit) const; + Ref<KinematicCollision2D> _move(const Vector2 &p_motion, bool p_test_only = false, real_t p_margin = 0.08); - void set_collision_layer_bit(int p_bit, bool p_value); - bool get_collision_layer_bit(int p_bit) const; +public: + 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); - Array get_collision_exceptions(); + TypedArray<PhysicsBody2D> get_collision_exceptions(); void add_collision_exception_with(Node *p_node); //must be physicsbody void remove_collision_exception_with(Node *p_node); - PhysicsBody2D(); + virtual ~PhysicsBody2D(); }; class StaticBody2D : public PhysicsBody2D { - GDCLASS(StaticBody2D, PhysicsBody2D); +private: Vector2 constant_linear_velocity; - real_t constant_angular_velocity; + real_t constant_angular_velocity = 0.0; Ref<PhysicsMaterial> physics_material_override; @@ -96,25 +82,53 @@ public: Vector2 get_constant_linear_velocity() const; real_t get_constant_angular_velocity() const; - StaticBody2D(); - ~StaticBody2D(); + StaticBody2D(PhysicsServer2D::BodyMode p_mode = PhysicsServer2D::BODY_MODE_STATIC); private: void _reload_physics_characteristics(); }; -class RigidBody2D : public PhysicsBody2D { +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_sync_to_physics(bool p_enable); + bool is_sync_to_physics_enabled() const; +}; + +class RigidBody2D : public PhysicsBody2D { GDCLASS(RigidBody2D, PhysicsBody2D); public: enum Mode { - MODE_RIGID, + MODE_DYNAMIC, MODE_STATIC, - MODE_CHARACTER, + MODE_DYNAMIC_LOCKED, MODE_KINEMATIC, }; + enum CenterOfMassMode { + CENTER_OF_MASS_MODE_AUTO, + CENTER_OF_MASS_MODE_CUSTOM, + }; + enum CCDMode { CCD_MODE_DISABLED, CCD_MODE_CAST_RAY, @@ -122,34 +136,37 @@ public: }; private: - bool can_sleep; - PhysicsDirectBodyState2D *state; - Mode mode; + bool can_sleep = true; + 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; - real_t mass; Ref<PhysicsMaterial> physics_material_override; - real_t gravity_scale; - real_t linear_damp; - real_t angular_damp; + real_t gravity_scale = 1.0; + real_t linear_damp = -1.0; + real_t angular_damp = -1.0; Vector2 linear_velocity; - real_t angular_velocity; - bool sleeping; + real_t angular_velocity = 0.0; + bool sleeping = false; - int max_contacts_reported; + int max_contacts_reported = 0; - bool custom_integrator; + bool custom_integrator = false; - CCDMode ccd_mode; + CCDMode ccd_mode = CCD_MODE_DISABLED; struct ShapePair { - - int body_shape; - int local_shape; - bool tagged; + int body_shape = 0; + int local_shape = 0; + bool tagged = false; bool operator<(const ShapePair &p_sp) const { - if (body_shape == p_sp.body_shape) + if (body_shape == p_sp.body_shape) { return local_shape < p_sp.local_shape; + } return body_shape < p_sp.body_shape; } @@ -161,36 +178,39 @@ private: } }; struct RigidBody2D_RemoveAction { - + RID rid; ObjectID body_id; ShapePair pair; }; struct BodyState { - + RID rid; //int rc; - bool in_scene; + bool in_scene = false; VSet<ShapePair> shapes; }; struct ContactMonitor { - - bool locked; + bool locked = false; Map<ObjectID, BodyState> body_map; }; - ContactMonitor *contact_monitor; + ContactMonitor *contact_monitor = nullptr; void _body_enter_tree(ObjectID p_id); void _body_exit_tree(ObjectID p_id); - void _body_inout(int p_status, ObjectID p_instance, int p_body_shape, int p_local_shape); - void _direct_state_changed(Object *p_state); + void _body_inout(int p_status, const RID &p_body, ObjectID p_instance, int p_body_shape, int p_local_shape); - bool _test_motion(const Vector2 &p_motion, bool p_infinite_inertia = true, float p_margin = 0.08, const Ref<PhysicsTestMotionResult2D> &p_result = Ref<PhysicsTestMotionResult2D>()); + 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; @@ -201,8 +221,11 @@ public: void set_inertia(real_t p_inertia); real_t get_inertia() const; - void set_weight(real_t p_weight); - real_t get_weight() 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,22 +266,22 @@ public: CCDMode get_continuous_collision_detection_mode() const; void apply_central_impulse(const Vector2 &p_impulse); - void apply_impulse(const Vector2 &p_offset, const Vector2 &p_impulse); - void apply_torque_impulse(float p_torque); + void apply_impulse(const Vector2 &p_impulse, const Vector2 &p_position = Vector2()); + void apply_torque_impulse(real_t p_torque); void set_applied_force(const Vector2 &p_force); Vector2 get_applied_force() const; - void set_applied_torque(const float p_torque); - float get_applied_torque() const; + void set_applied_torque(const real_t p_torque); + real_t get_applied_torque() const; void add_central_force(const Vector2 &p_force); - void add_force(const Vector2 &p_offset, const Vector2 &p_force); - void add_torque(float p_torque); + void add_force(const Vector2 &p_force, const Vector2 &p_position = Vector2()); + void add_torque(real_t p_torque); - Array get_colliding_bodies() const; //function for script + TypedArray<Node2D> get_colliding_bodies() const; //function for script - virtual String get_configuration_warning() const; + virtual TypedArray<String> get_configuration_warnings() const override; RigidBody2D(); ~RigidBody2D(); @@ -268,88 +291,129 @@ private: }; VARIANT_ENUM_CAST(RigidBody2D::Mode); +VARIANT_ENUM_CAST(RigidBody2D::CenterOfMassMode); VARIANT_ENUM_CAST(RigidBody2D::CCDMode); -class KinematicBody2D : public PhysicsBody2D { - - GDCLASS(KinematicBody2D, PhysicsBody2D); +class CharacterBody2D : public PhysicsBody2D { + GDCLASS(CharacterBody2D, PhysicsBody2D); public: - struct Collision { - Vector2 collision; - Vector2 normal; - Vector2 collider_vel; - ObjectID collider; - RID collider_rid; - int collider_shape; - Variant collider_metadata; - Vector2 remainder; - Vector2 travel; - int local_shape; + 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: - float margin; + real_t margin = 0.08; + MotionMode motion_mode = MOTION_MODE_GROUNDED; + + 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); + 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; - bool on_floor; - bool on_ceiling; - bool on_wall; - bool sync_to_physics; - - Vector<Collision> colliders; + Vector2 platform_velocity; + RID platform_rid; + bool on_floor = false; + bool on_ceiling = false; + bool on_wall = false; + + Vector<PhysicsServer2D::MotionResult> motion_results; Vector<Ref<KinematicCollision2D>> slide_colliders; - Ref<KinematicCollision2D> motion_cache; - _FORCE_INLINE_ bool _ignores_mode(PhysicsServer2D::BodyMode) const; + void set_safe_margin(real_t p_margin); + real_t get_safe_margin() const; - Ref<KinematicCollision2D> _move(const Vector2 &p_motion, bool p_infinite_inertia = true, bool p_exclude_raycast_shapes = true, bool p_test_only = false); - Ref<KinematicCollision2D> _get_slide_collision(int p_bounce); + bool is_floor_stop_on_slope_enabled() const; + void set_floor_stop_on_slope_enabled(bool p_enabled); - Transform2D last_valid_transform; - void _direct_state_changed(Object *p_state); + bool is_floor_constant_speed_enabled() const; + void set_floor_constant_speed_enabled(bool p_enabled); -protected: - void _notification(int p_what); - static void _bind_methods(); + bool is_floor_block_on_wall_enabled() const; + void set_floor_block_on_wall_enabled(bool p_enabled); -public: - bool move_and_collide(const Vector2 &p_motion, bool p_infinite_inertia, Collision &r_collision, bool p_exclude_raycast_shapes = true, bool p_test_only = false); + bool is_slide_on_ceiling_enabled() const; + void set_slide_on_ceiling_enabled(bool p_enabled); - bool test_move(const Transform2D &p_from, const Vector2 &p_motion, bool p_infinite_inertia = true); + int get_max_slides() const; + void set_max_slides(int p_max_slides); - bool separate_raycast_shapes(bool p_infinite_inertia, Collision &r_collision); + real_t get_floor_max_angle() const; + void set_floor_max_angle(real_t p_radians); - void set_safe_margin(float p_margin); - float get_safe_margin() const; + real_t get_floor_snap_length(); + void set_floor_snap_length(real_t p_floor_snap_length); - Vector2 move_and_slide(const Vector2 &p_linear_velocity, const Vector2 &p_up_direction = Vector2(0, 0), bool p_stop_on_slope = false, int p_max_slides = 4, float p_floor_max_angle = Math::deg2rad((float)45), bool p_infinite_inertia = true); - Vector2 move_and_slide_with_snap(const Vector2 &p_linear_velocity, const Vector2 &p_snap, const Vector2 &p_up_direction = Vector2(0, 0), bool p_stop_on_slope = false, int p_max_slides = 4, float p_floor_max_angle = Math::deg2rad((float)45), bool p_infinite_inertia = true); - 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; + real_t get_free_mode_min_slide_angle() const; + void set_free_mode_min_slide_angle(real_t p_radians); - int get_slide_count() const; - Collision get_slide_collision(int p_bounce) const; + uint32_t get_moving_platform_floor_layers() const; + void set_moving_platform_floor_layers(const uint32_t p_exclude_layer); - void set_sync_to_physics(bool p_enable); - bool is_sync_to_physics_enabled() const; + uint32_t get_moving_platform_wall_layers() const; + void set_moving_platform_wall_layers(const uint32_t p_exclude_layer); + + void set_motion_mode(MotionMode p_mode); + MotionMode get_motion_mode() 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); + + 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); - KinematicBody2D(); - ~KinematicBody2D(); +protected: + void _notification(int p_what); + static void _bind_methods(); + virtual void _validate_property(PropertyInfo &property) const override; }; -class KinematicCollision2D : public Reference { +VARIANT_ENUM_CAST(CharacterBody2D::MotionMode); - GDCLASS(KinematicCollision2D, Reference); +class KinematicCollision2D : public RefCounted { + GDCLASS(KinematicCollision2D, RefCounted); - KinematicBody2D *owner; - friend class KinematicBody2D; - KinematicBody2D::Collision collision; + PhysicsBody2D *owner = nullptr; + friend class PhysicsBody2D; + friend class CharacterBody2D; + PhysicsServer2D::MotionResult result; protected: static void _bind_methods(); @@ -359,15 +423,15 @@ 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; + RID get_collider_rid() const; Object *get_collider_shape() const; int get_collider_shape_index() const; Vector2 get_collider_velocity() const; Variant get_collider_metadata() const; - - KinematicCollision2D(); }; #endif // PHYSICS_BODY_2D_H diff --git a/scene/2d/polygon_2d.cpp b/scene/2d/polygon_2d.cpp index 84c1828b47..7366be5a7d 100644 --- a/scene/2d/polygon_2d.cpp +++ b/scene/2d/polygon_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,7 +30,7 @@ #include "polygon_2d.h" -#include "core/math/geometry.h" +#include "core/math/geometry_2d.h" #include "skeleton_2d.h" #ifdef TOOLS_ENABLED @@ -65,10 +65,11 @@ Rect2 Polygon2D::_edit_get_rect() const { item_rect = Rect2(); for (int i = 0; i < l; i++) { Vector2 pos = r[i] + offset; - if (i == 0) + if (i == 0) { item_rect.position = pos; - else + } else { item_rect.expand_to(pos); + } } rect_cache_dirty = false; } @@ -81,27 +82,30 @@ bool Polygon2D::_edit_use_rect() const { } bool Polygon2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { - Vector<Vector2> polygon2d = Variant(polygon); if (internal_vertices > 0) { polygon2d.resize(polygon2d.size() - internal_vertices); } - return Geometry::is_point_in_polygon(p_point - get_offset(), polygon2d); + return Geometry2D::is_point_in_polygon(p_point - get_offset(), polygon2d); } #endif +void Polygon2D::_validate_property(PropertyInfo &property) const { + if (!invert && property.name == "invert_border") { + property.usage = PROPERTY_USAGE_NOEDITOR; + } +} + void Polygon2D::_skeleton_bone_setup_changed() { update(); } void Polygon2D::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_DRAW: { - - if (polygon.size() < 3) + if (polygon.size() < 3) { return; + } Skeleton2D *skeleton_node = nullptr; if (has_node(skeleton)) { @@ -147,7 +151,6 @@ void Polygon2D::_notification(int p_what) { points.resize(len); { - const Vector2 *polyr = polygon.ptr(); for (int i = 0; i < len; i++) { points.write[i] = polyr[i] + offset; @@ -155,17 +158,17 @@ void Polygon2D::_notification(int p_what) { } if (invert) { - Rect2 bounds; int highest_idx = -1; - float highest_y = -1e20; - float sum = 0; + real_t highest_y = -1e20; + real_t sum = 0.0; for (int i = 0; i < len; i++) { - if (i == 0) + if (i == 0) { bounds.position = points[i]; - else + } else { bounds.expand_to(points[i]); + } if (points[i].y > highest_y) { highest_idx = i; highest_y = points[i].y; @@ -195,12 +198,10 @@ void Polygon2D::_notification(int p_what) { points.resize(points.size() + 7); for (int i = points.size() - 1; i >= highest_idx + 7; i--) { - points.write[i] = points[i - 7]; } for (int i = 0; i < 7; i++) { - points.write[highest_idx + i + 1] = ep[i]; } @@ -208,7 +209,6 @@ void Polygon2D::_notification(int p_what) { } if (texture.is_valid()) { - Transform2D texmat(tex_rot, tex_ofs); texmat.scale(tex_scale); Size2 tex_size = texture->get_size(); @@ -216,7 +216,6 @@ void Polygon2D::_notification(int p_what) { uvs.resize(len); if (points.size() == uv.size()) { - const Vector2 *uvr = uv.ptr(); for (int i = 0; i < len; i++) { @@ -259,8 +258,9 @@ void Polygon2D::_notification(int p_what) { int bone_index = bone->get_index_in_skeleton(); const float *r = bone_weights[i].weights.ptr(); for (int j = 0; j < vc; j++) { - if (r[j] == 0.0) + if (r[j] == 0.0) { continue; //weight is unpainted, skip + } //find an index with a weight for (int k = 0; k < 4; k++) { if (weightsw[j * 4 + k] < r[j]) { @@ -279,12 +279,13 @@ void Polygon2D::_notification(int p_what) { //normalize the weights for (int i = 0; i < vc; i++) { - float tw = 0; + real_t tw = 0.0; for (int j = 0; j < 4; j++) { tw += weightsw[i * 4 + j]; } - if (tw == 0) + if (tw == 0) { continue; //unpainted, do nothing + } //normalize for (int j = 0; j < 4; j++) { @@ -301,22 +302,24 @@ void Polygon2D::_notification(int p_what) { colors.write[i] = color_r[i]; } } else { - colors.push_back(color); + colors.resize(len); + for (int i = 0; i < len; i++) { + colors.write[i] = color; + } } + Vector<int> index_array; + if (invert || polygons.size() == 0) { - Vector<int> indices = Geometry::triangulate_polygon(points); - if (indices.size()) { - RS::get_singleton()->canvas_item_add_triangle_array(get_canvas_item(), indices, points, colors, uvs, bones, weights, texture.is_valid() ? texture->get_rid() : RID(), -1, normal_map.is_valid() ? normal_map->get_rid() : RID(), specular_map.is_valid() ? specular_map->get_rid() : RID(), Color(specular_color.r, specular_color.g, specular_color.b, shininess)); - } + index_array = Geometry2D::triangulate_polygon(points); } else { //draw individual polygons - Vector<int> total_indices; for (int i = 0; i < polygons.size(); i++) { Vector<int> src_indices = polygons[i]; int ic = src_indices.size(); - if (ic < 3) + if (ic < 3) { continue; + } const int *r = src_indices.ptr(); Vector<Vector2> tmp_points; @@ -327,22 +330,42 @@ void Polygon2D::_notification(int p_what) { ERR_CONTINUE(idx < 0 || idx >= points.size()); tmp_points.write[j] = points[r[j]]; } - Vector<int> indices = Geometry::triangulate_polygon(tmp_points); + Vector<int> indices = Geometry2D::triangulate_polygon(tmp_points); int ic2 = indices.size(); const int *r2 = indices.ptr(); - int bic = total_indices.size(); - total_indices.resize(bic + ic2); - int *w2 = total_indices.ptrw(); + int bic = index_array.size(); + index_array.resize(bic + ic2); + int *w2 = index_array.ptrw(); for (int j = 0; j < ic2; j++) { w2[j + bic] = r[r2[j]]; } } + } + + RS::get_singleton()->mesh_clear(mesh); + + if (index_array.size()) { + Array arr; + arr.resize(RS::ARRAY_MAX); + arr[RS::ARRAY_VERTEX] = points; + if (uvs.size() == points.size()) { + arr[RS::ARRAY_TEX_UV] = uvs; + } + if (colors.size() == points.size()) { + arr[RS::ARRAY_COLOR] = colors; + } - if (total_indices.size()) { - RS::get_singleton()->canvas_item_add_triangle_array(get_canvas_item(), total_indices, points, colors, uvs, bones, weights, texture.is_valid() ? texture->get_rid() : RID()); + if (bones.size() == points.size() * 4) { + arr[RS::ARRAY_BONES] = bones; + arr[RS::ARRAY_WEIGHTS] = weights; } + + arr[RS::ARRAY_INDEX] = index_array; + + RS::get_singleton()->mesh_add_surface_from_arrays(mesh, RS::PRIMITIVE_TRIANGLES, arr, Array(), Dictionary(), RS::ARRAY_FLAG_USE_2D_VERTICES); + RS::get_singleton()->canvas_item_add_mesh(get_canvas_item(), mesh, Transform2D(), Color(1, 1, 1), texture.is_valid() ? texture->get_rid() : RID()); } } break; @@ -356,12 +379,10 @@ void Polygon2D::set_polygon(const Vector<Vector2> &p_polygon) { } Vector<Vector2> Polygon2D::get_polygon() const { - return polygon; } void Polygon2D::set_internal_vertex_count(int p_count) { - internal_vertices = p_count; } @@ -370,49 +391,42 @@ int Polygon2D::get_internal_vertex_count() const { } void Polygon2D::set_uv(const Vector<Vector2> &p_uv) { - uv = p_uv; update(); } Vector<Vector2> Polygon2D::get_uv() const { - return uv; } void Polygon2D::set_polygons(const Array &p_polygons) { - polygons = p_polygons; update(); } Array Polygon2D::get_polygons() const { - return polygons; } void Polygon2D::set_color(const Color &p_color) { - color = p_color; update(); } -Color Polygon2D::get_color() const { +Color Polygon2D::get_color() const { return color; } void Polygon2D::set_vertex_colors(const Vector<Color> &p_colors) { - vertex_colors = p_colors; update(); } -Vector<Color> Polygon2D::get_vertex_colors() const { +Vector<Color> Polygon2D::get_vertex_colors() const { return vertex_colors; } void Polygon2D::set_texture(const Ref<Texture2D> &p_texture) { - texture = p_texture; /*if (texture.is_valid()) { @@ -425,150 +439,98 @@ void Polygon2D::set_texture(const Ref<Texture2D> &p_texture) { }*/ update(); } -Ref<Texture2D> Polygon2D::get_texture() const { +Ref<Texture2D> Polygon2D::get_texture() const { return texture; } -void Polygon2D::set_normal_map(const Ref<Texture2D> &p_normal_map) { - normal_map = p_normal_map; - update(); -} - -Ref<Texture2D> Polygon2D::get_normal_map() const { - return normal_map; -} - -void Polygon2D::set_specular_map(const Ref<Texture2D> &p_specular_map) { - specular_map = p_specular_map; - update(); -} - -Ref<Texture2D> Polygon2D::get_specular_map() const { - return specular_map; -} - -void Polygon2D::set_specular_color(const Color &p_specular_color) { - specular_color = p_specular_color; - update(); -} - -Color Polygon2D::get_specular_color() const { - return specular_color; -} - -void Polygon2D::set_shininess(float p_shininess) { - shininess = CLAMP(p_shininess, 0.0, 1.0); - update(); -} - -float Polygon2D::get_shininess() const { - return shininess; -} - void Polygon2D::set_texture_offset(const Vector2 &p_offset) { - tex_ofs = p_offset; update(); } -Vector2 Polygon2D::get_texture_offset() const { +Vector2 Polygon2D::get_texture_offset() const { return tex_ofs; } -void Polygon2D::set_texture_rotation(float p_rot) { - +void Polygon2D::set_texture_rotation(real_t p_rot) { tex_rot = p_rot; update(); } -float Polygon2D::get_texture_rotation() const { +real_t Polygon2D::get_texture_rotation() const { return tex_rot; } -void Polygon2D::set_texture_rotation_degrees(float p_rot) { - - set_texture_rotation(Math::deg2rad(p_rot)); -} -float Polygon2D::get_texture_rotation_degrees() const { - - return Math::rad2deg(get_texture_rotation()); -} - void Polygon2D::set_texture_scale(const Size2 &p_scale) { - tex_scale = p_scale; update(); } -Size2 Polygon2D::get_texture_scale() const { +Size2 Polygon2D::get_texture_scale() const { return tex_scale; } void Polygon2D::set_invert(bool p_invert) { - invert = p_invert; update(); + notify_property_list_changed(); } -bool Polygon2D::get_invert() const { +bool Polygon2D::get_invert() const { return invert; } void Polygon2D::set_antialiased(bool p_antialiased) { - antialiased = p_antialiased; update(); } -bool Polygon2D::get_antialiased() const { +bool Polygon2D::get_antialiased() const { return antialiased; } -void Polygon2D::set_invert_border(float p_invert_border) { - +void Polygon2D::set_invert_border(real_t p_invert_border) { invert_border = p_invert_border; update(); } -float Polygon2D::get_invert_border() const { +real_t Polygon2D::get_invert_border() const { return invert_border; } void Polygon2D::set_offset(const Vector2 &p_offset) { - offset = p_offset; rect_cache_dirty = true; update(); - _change_notify("offset"); } Vector2 Polygon2D::get_offset() const { - return offset; } void Polygon2D::add_bone(const NodePath &p_path, const Vector<float> &p_weights) { - Bone bone; bone.path = p_path; bone.weights = p_weights; bone_weights.push_back(bone); } + int Polygon2D::get_bone_count() const { return bone_weights.size(); } + NodePath Polygon2D::get_bone_path(int p_index) const { ERR_FAIL_INDEX_V(p_index, bone_weights.size(), NodePath()); return bone_weights[p_index].path; } -Vector<float> Polygon2D::get_bone_weights(int p_index) const { +Vector<float> Polygon2D::get_bone_weights(int p_index) const { ERR_FAIL_INDEX_V(p_index, bone_weights.size(), Vector<float>()); return bone_weights[p_index].weights; } -void Polygon2D::erase_bone(int p_idx) { +void Polygon2D::erase_bone(int p_idx) { ERR_FAIL_INDEX(p_idx, bone_weights.size()); bone_weights.remove(p_idx); } @@ -582,6 +544,7 @@ void Polygon2D::set_bone_weights(int p_index, const Vector<float> &p_weights) { bone_weights.write[p_index].weights = p_weights; update(); } + void Polygon2D::set_bone_path(int p_index, const NodePath &p_path) { ERR_FAIL_INDEX(p_index, bone_weights.size()); bone_weights.write[p_index].path = p_path; @@ -596,8 +559,8 @@ Array Polygon2D::_get_bones() const { } return bones; } -void Polygon2D::_set_bones(const Array &p_bones) { +void Polygon2D::_set_bones(const Array &p_bones) { ERR_FAIL_COND(p_bones.size() & 1); clear_bones(); for (int i = 0; i < p_bones.size(); i += 2) { @@ -606,8 +569,9 @@ void Polygon2D::_set_bones(const Array &p_bones) { } void Polygon2D::set_skeleton(const NodePath &p_skeleton) { - if (skeleton == p_skeleton) + if (skeleton == p_skeleton) { return; + } skeleton = p_skeleton; update(); } @@ -617,7 +581,6 @@ NodePath Polygon2D::get_skeleton() const { } void Polygon2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_polygon", "polygon"), &Polygon2D::set_polygon); ClassDB::bind_method(D_METHOD("get_polygon"), &Polygon2D::get_polygon); @@ -636,27 +599,12 @@ void Polygon2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_texture", "texture"), &Polygon2D::set_texture); ClassDB::bind_method(D_METHOD("get_texture"), &Polygon2D::get_texture); - ClassDB::bind_method(D_METHOD("set_normal_map", "normal_map"), &Polygon2D::set_normal_map); - ClassDB::bind_method(D_METHOD("get_normal_map"), &Polygon2D::get_normal_map); - - ClassDB::bind_method(D_METHOD("set_specular_map", "specular_map"), &Polygon2D::set_specular_map); - ClassDB::bind_method(D_METHOD("get_specular_map"), &Polygon2D::get_specular_map); - - ClassDB::bind_method(D_METHOD("set_specular_color", "specular_color"), &Polygon2D::set_specular_color); - ClassDB::bind_method(D_METHOD("get_specular_color"), &Polygon2D::get_specular_color); - - ClassDB::bind_method(D_METHOD("set_shininess", "shininess"), &Polygon2D::set_shininess); - ClassDB::bind_method(D_METHOD("get_shininess"), &Polygon2D::get_shininess); - ClassDB::bind_method(D_METHOD("set_texture_offset", "texture_offset"), &Polygon2D::set_texture_offset); ClassDB::bind_method(D_METHOD("get_texture_offset"), &Polygon2D::get_texture_offset); ClassDB::bind_method(D_METHOD("set_texture_rotation", "texture_rotation"), &Polygon2D::set_texture_rotation); ClassDB::bind_method(D_METHOD("get_texture_rotation"), &Polygon2D::get_texture_rotation); - ClassDB::bind_method(D_METHOD("set_texture_rotation_degrees", "texture_rotation"), &Polygon2D::set_texture_rotation_degrees); - ClassDB::bind_method(D_METHOD("get_texture_rotation_degrees"), &Polygon2D::get_texture_rotation_degrees); - ClassDB::bind_method(D_METHOD("set_texture_scale", "texture_scale"), &Polygon2D::set_texture_scale); ClassDB::bind_method(D_METHOD("get_texture_scale"), &Polygon2D::get_texture_scale); @@ -698,13 +646,7 @@ void Polygon2D::_bind_methods() { ADD_GROUP("Texture2D", "texture_"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "texture_offset"), "set_texture_offset", "get_texture_offset"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "texture_scale"), "set_texture_scale", "get_texture_scale"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "texture_rotation_degrees", PROPERTY_HINT_RANGE, "-360,360,0.1,or_lesser,or_greater"), "set_texture_rotation_degrees", "get_texture_rotation_degrees"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "texture_rotation", PROPERTY_HINT_NONE, "", 0), "set_texture_rotation", "get_texture_rotation"); - ADD_GROUP("Lighting", ""); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "normal_map", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_normal_map", "get_normal_map"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "specular_map", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_specular_map", "get_specular_map"); - ADD_PROPERTY(PropertyInfo(Variant::COLOR, "specular_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_specular_color", "get_specular_color"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "shininess", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_shininess", "get_shininess"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "texture_rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_lesser,or_greater,radians"), "set_texture_rotation", "get_texture_rotation"); ADD_GROUP("Skeleton", ""); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "skeleton", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton2D"), "set_skeleton", "get_skeleton"); @@ -722,17 +664,9 @@ void Polygon2D::_bind_methods() { } Polygon2D::Polygon2D() { + mesh = RS::get_singleton()->mesh_create(); +} - invert = 0; - invert_border = 100; - antialiased = false; - tex_rot = 0; - tex_tile = true; - tex_scale = Vector2(1, 1); - color = Color(1, 1, 1); - rect_cache_dirty = true; - internal_vertices = 0; - - specular_color = Color(1, 1, 1, 1); - shininess = 1.0; +Polygon2D::~Polygon2D() { + RS::get_singleton()->free(mesh); } diff --git a/scene/2d/polygon_2d.h b/scene/2d/polygon_2d.h index 777c1f82f3..bf386b9ace 100644 --- a/scene/2d/polygon_2d.h +++ b/scene/2d/polygon_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -34,14 +34,13 @@ #include "scene/2d/node_2d.h" class Polygon2D : public Node2D { - GDCLASS(Polygon2D, Node2D); Vector<Vector2> polygon; Vector<Vector2> uv; Vector<Color> vertex_colors; Array polygons; - int internal_vertices; + int internal_vertices = 0; struct Bone { NodePath path; @@ -50,23 +49,19 @@ class Polygon2D : public Node2D { Vector<Bone> bone_weights; - Color color; + Color color = Color(1, 1, 1); Ref<Texture2D> texture; - Ref<Texture2D> normal_map; - Ref<Texture2D> specular_map; - Color specular_color; - float shininess; - Size2 tex_scale; + Size2 tex_scale = Vector2(1, 1); Vector2 tex_ofs; - bool tex_tile; - float tex_rot; - bool invert; - float invert_border; - bool antialiased; + bool tex_tile = true; + real_t tex_rot = 0.0; + bool invert = false; + real_t invert_border = 100.0; + bool antialiased = false; Vector2 offset; - mutable bool rect_cache_dirty; + mutable bool rect_cache_dirty = true; mutable Rect2 item_rect; NodePath skeleton; @@ -77,22 +72,25 @@ class Polygon2D : public Node2D { void _skeleton_bone_setup_changed(); + RID mesh; + protected: void _notification(int p_what); static void _bind_methods(); + void _validate_property(PropertyInfo &property) const override; public: #ifdef TOOLS_ENABLED - virtual Dictionary _edit_get_state() const; - virtual void _edit_set_state(const Dictionary &p_state); + virtual Dictionary _edit_get_state() const override; + virtual void _edit_set_state(const Dictionary &p_state) override; - virtual void _edit_set_pivot(const Point2 &p_pivot); - virtual Point2 _edit_get_pivot() const; - virtual bool _edit_use_pivot() const; - virtual Rect2 _edit_get_rect() const; - virtual bool _edit_use_rect() const; + virtual void _edit_set_pivot(const Point2 &p_pivot) override; + virtual Point2 _edit_get_pivot() const override; + virtual bool _edit_use_pivot() const override; + virtual Rect2 _edit_get_rect() const override; + virtual bool _edit_use_rect() const override; - virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const; + virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override; #endif void set_polygon(const Vector<Vector2> &p_polygon); @@ -116,26 +114,11 @@ public: void set_texture(const Ref<Texture2D> &p_texture); Ref<Texture2D> get_texture() const; - void set_normal_map(const Ref<Texture2D> &p_normal_map); - Ref<Texture2D> get_normal_map() const; - - void set_specular_map(const Ref<Texture2D> &p_specular_map); - Ref<Texture2D> get_specular_map() const; - - void set_specular_color(const Color &p_specular_color); - Color get_specular_color() const; - - void set_shininess(float p_shininess); - float get_shininess() const; - void set_texture_offset(const Vector2 &p_offset); Vector2 get_texture_offset() const; - void set_texture_rotation(float p_rot); - float get_texture_rotation() const; - - void set_texture_rotation_degrees(float p_rot); - float get_texture_rotation_degrees() const; + void set_texture_rotation(real_t p_rot); + real_t get_texture_rotation() const; void set_texture_scale(const Size2 &p_scale); Size2 get_texture_scale() const; @@ -146,8 +129,8 @@ public: void set_antialiased(bool p_antialiased); bool get_antialiased() const; - void set_invert_border(float p_invert_border); - float get_invert_border() const; + void set_invert_border(real_t p_invert_border); + real_t get_invert_border() const; void set_offset(const Vector2 &p_offset); Vector2 get_offset() const; @@ -165,6 +148,7 @@ public: NodePath get_skeleton() const; Polygon2D(); + ~Polygon2D(); }; #endif // POLYGON_2D_H diff --git a/scene/2d/position_2d.cpp b/scene/2d/position_2d.cpp index 9e95a55d9f..4f053ff8b0 100644 --- a/scene/2d/position_2d.cpp +++ b/scene/2d/position_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,23 +30,49 @@ #include "position_2d.h" -#include "core/engine.h" -#include "scene/resources/texture.h" - -const float DEFAULT_GIZMO_EXTENTS = 10.0; +const real_t DEFAULT_GIZMO_EXTENTS = 10.0; void Position2D::_draw_cross() { - - float extents = get_gizmo_extents(); - // Colors taken from `axis_x_color` and `axis_y_color` (defined in `editor/editor_themes.cpp`) - draw_line(Point2(-extents, 0), Point2(+extents, 0), Color(0.96, 0.20, 0.32)); - draw_line(Point2(0, -extents), Point2(0, +extents), Color(0.53, 0.84, 0.01)); + const real_t extents = get_gizmo_extents(); + + // Add more points to create a "hard stop" in the color gradient. + PackedVector2Array points_x; + points_x.push_back(Point2(+extents, 0)); + points_x.push_back(Point2()); + points_x.push_back(Point2()); + points_x.push_back(Point2(-extents, 0)); + + PackedVector2Array points_y; + points_y.push_back(Point2(0, +extents)); + points_y.push_back(Point2()); + points_y.push_back(Point2()); + points_y.push_back(Point2(0, -extents)); + + // Use the axis color which is brighter for the positive axis. + // Use a darkened axis color for the negative axis. + // This makes it possible to see in which direction the Position3D node is rotated + // (which can be important depending on how it's used). + // Axis colors are taken from `axis_x_color` and `axis_y_color` (defined in `editor/editor_themes.cpp`). + PackedColorArray colors_x; + const Color color_x = Color(0.96, 0.20, 0.32); + colors_x.push_back(color_x); + colors_x.push_back(color_x); + colors_x.push_back(color_x.lerp(Color(0, 0, 0), 0.5)); + colors_x.push_back(color_x.lerp(Color(0, 0, 0), 0.5)); + draw_multiline_colors(points_x, colors_x); + + PackedColorArray colors_y; + const Color color_y = Color(0.53, 0.84, 0.01); + colors_y.push_back(color_y); + colors_y.push_back(color_y); + colors_y.push_back(color_y.lerp(Color(0, 0, 0), 0.5)); + colors_y.push_back(color_y.lerp(Color(0, 0, 0), 0.5)); + draw_multiline_colors(points_y, colors_y); } #ifdef TOOLS_ENABLED Rect2 Position2D::_edit_get_rect() const { - - float extents = get_gizmo_extents(); + real_t extents = get_gizmo_extents(); return Rect2(Point2(-extents, -extents), Size2(extents * 2, extents * 2)); } @@ -56,24 +82,23 @@ bool Position2D::_edit_use_rect() const { #endif void Position2D::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - update(); } break; case NOTIFICATION_DRAW: { - if (!is_inside_tree()) + if (!is_inside_tree()) { break; - if (Engine::get_singleton()->is_editor_hint()) + } + if (Engine::get_singleton()->is_editor_hint()) { _draw_cross(); + } } break; } } -void Position2D::set_gizmo_extents(float p_extents) { +void Position2D::set_gizmo_extents(real_t p_extents) { if (p_extents == DEFAULT_GIZMO_EXTENTS) { set_meta("_gizmo_extents_", Variant()); } else { @@ -83,7 +108,7 @@ void Position2D::set_gizmo_extents(float p_extents) { update(); } -float Position2D::get_gizmo_extents() const { +real_t Position2D::get_gizmo_extents() const { if (has_meta("_gizmo_extents_")) { return get_meta("_gizmo_extents_"); } else { @@ -92,7 +117,6 @@ float Position2D::get_gizmo_extents() const { } void Position2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("_set_gizmo_extents", "extents"), &Position2D::set_gizmo_extents); ClassDB::bind_method(D_METHOD("_get_gizmo_extents"), &Position2D::get_gizmo_extents); diff --git a/scene/2d/position_2d.h b/scene/2d/position_2d.h index 3ea636171d..9ed622c8f6 100644 --- a/scene/2d/position_2d.h +++ b/scene/2d/position_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -34,7 +34,6 @@ #include "scene/2d/node_2d.h" class Position2D : public Node2D { - GDCLASS(Position2D, Node2D); void _draw_cross(); @@ -45,12 +44,12 @@ protected: public: #ifdef TOOLS_ENABLED - virtual Rect2 _edit_get_rect() const; - virtual bool _edit_use_rect() const; + virtual Rect2 _edit_get_rect() const override; + virtual bool _edit_use_rect() const override; #endif - void set_gizmo_extents(float p_extents); - float get_gizmo_extents() const; + void set_gizmo_extents(real_t p_extents); + real_t get_gizmo_extents() const; Position2D(); }; diff --git a/scene/2d/ray_cast_2d.cpp b/scene/2d/ray_cast_2d.cpp index 9d6c7304ce..3ac2128c2e 100644 --- a/scene/2d/ray_cast_2d.cpp +++ b/scene/2d/ray_cast_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,169 +31,143 @@ #include "ray_cast_2d.h" #include "collision_object_2d.h" -#include "core/engine.h" -#include "physics_body_2d.h" -#include "servers/physics_server_2d.h" -void RayCast2D::set_cast_to(const Vector2 &p_point) { - - cast_to = p_point; - if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_collisions_hint())) +void RayCast2D::set_target_position(const Vector2 &p_point) { + target_position = p_point; + if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_collisions_hint())) { update(); + } } -Vector2 RayCast2D::get_cast_to() const { - - return cast_to; +Vector2 RayCast2D::get_target_position() const { + return target_position; } void RayCast2D::set_collision_mask(uint32_t p_mask) { - collision_mask = p_mask; } uint32_t RayCast2D::get_collision_mask() const { - return collision_mask; } -void RayCast2D::set_collision_mask_bit(int p_bit, bool p_value) { - +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; - else - mask &= ~(1 << p_bit); + if (p_value) { + mask |= 1 << (p_layer_number - 1); + } else { + mask &= ~(1 << (p_layer_number - 1)); + } set_collision_mask(mask); } -bool RayCast2D::get_collision_mask_bit(int p_bit) const { - - 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 { - return collided; } -Object *RayCast2D::get_collider() const { - if (against.is_null()) +Object *RayCast2D::get_collider() const { + if (against.is_null()) { return nullptr; + } return ObjectDB::get_instance(against); } int RayCast2D::get_collider_shape() const { - return against_shape; } -Vector2 RayCast2D::get_collision_point() const { +Vector2 RayCast2D::get_collision_point() const { return collision_point; } -Vector2 RayCast2D::get_collision_normal() const { +Vector2 RayCast2D::get_collision_normal() const { return collision_normal; } void RayCast2D::set_enabled(bool p_enabled) { - enabled = p_enabled; update(); - if (is_inside_tree() && !Engine::get_singleton()->is_editor_hint()) + if (is_inside_tree() && !Engine::get_singleton()->is_editor_hint()) { set_physics_process_internal(p_enabled); - if (!p_enabled) + } + if (!p_enabled) { collided = false; + } } bool RayCast2D::is_enabled() const { - return enabled; } void RayCast2D::set_exclude_parent_body(bool p_exclude_parent_body) { - - if (exclude_parent_body == p_exclude_parent_body) + if (exclude_parent_body == p_exclude_parent_body) { return; + } exclude_parent_body = p_exclude_parent_body; - if (!is_inside_tree()) + if (!is_inside_tree()) { return; + } if (Object::cast_to<CollisionObject2D>(get_parent())) { - if (exclude_parent_body) + if (exclude_parent_body) { exclude.insert(Object::cast_to<CollisionObject2D>(get_parent())->get_rid()); - else + } else { exclude.erase(Object::cast_to<CollisionObject2D>(get_parent())->get_rid()); + } } } bool RayCast2D::get_exclude_parent_body() const { - return exclude_parent_body; } void RayCast2D::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - - if (enabled && !Engine::get_singleton()->is_editor_hint()) + if (enabled && !Engine::get_singleton()->is_editor_hint()) { set_physics_process_internal(true); - else + } else { set_physics_process_internal(false); + } if (Object::cast_to<CollisionObject2D>(get_parent())) { - if (exclude_parent_body) + if (exclude_parent_body) { exclude.insert(Object::cast_to<CollisionObject2D>(get_parent())->get_rid()); - else + } else { exclude.erase(Object::cast_to<CollisionObject2D>(get_parent())->get_rid()); + } } } break; case NOTIFICATION_EXIT_TREE: { - - if (enabled) + if (enabled) { set_physics_process_internal(false); + } } break; case NOTIFICATION_DRAW: { - - if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) + if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) { break; - Transform2D xf; - xf.rotate(cast_to.angle()); - xf.translate(Vector2(cast_to.length(), 0)); - - // Draw an arrow indicating where the RayCast is pointing to - Color draw_col = get_tree()->get_debug_collisions_color(); - if (!enabled) { - float g = draw_col.get_v(); - draw_col.r = g; - draw_col.g = g; - draw_col.b = g; } - draw_line(Vector2(), cast_to, draw_col, 2); - Vector<Vector2> pts; - float tsize = 8; - pts.push_back(xf.xform(Vector2(tsize, 0))); - pts.push_back(xf.xform(Vector2(0, Math_SQRT12 * tsize))); - pts.push_back(xf.xform(Vector2(0, -Math_SQRT12 * tsize))); - Vector<Color> cols; - for (int i = 0; i < 3; i++) - cols.push_back(draw_col); - - draw_primitive(pts, cols, Vector<Vector2>()); + _draw_debug_shape(); } break; case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { - - if (!enabled) + if (!enabled) { break; + } _update_raycast_state(); @@ -210,14 +184,14 @@ void RayCast2D::_update_raycast_state() { Transform2D gt = get_global_transform(); - Vector2 to = cast_to; - if (to == Vector2()) + Vector2 to = target_position; + if (to == Vector2()) { to = Vector2(0, 0.01); + } PhysicsDirectSpaceState2D::RayResult rr; - + bool prev_collision_state = collided; if (dss->intersect_ray(gt.get_origin(), gt.xform(to), rr, exclude, collision_mask, collide_with_bodies, collide_with_areas)) { - collided = true; against = rr.collider_id; collision_point = rr.position; @@ -228,6 +202,48 @@ void RayCast2D::_update_raycast_state() { against = ObjectID(); against_shape = 0; } + + if (prev_collision_state != collided) { + update(); + } +} + +void RayCast2D::_draw_debug_shape() { + Color draw_col = collided ? Color(1.0, 0.01, 0) : get_tree()->get_debug_collisions_color(); + if (!enabled) { + 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 real_t max_arrow_size = 6; + const real_t line_width = 1.4; + bool no_line = target_position.length() < line_width; + real_t arrow_size = CLAMP(target_position.length() * 2 / 3, line_width, max_arrow_size); + + if (no_line) { + arrow_size = target_position.length(); + } else { + draw_line(Vector2(), target_position - target_position.normalized() * arrow_size, draw_col, line_width); + } + + Transform2D xf; + xf.rotate(target_position.angle()); + xf.translate(Vector2(no_line ? 0 : target_position.length() - arrow_size, 0)); + + Vector<Vector2> pts; + pts.push_back(xf.xform(Vector2(arrow_size, 0))); + pts.push_back(xf.xform(Vector2(0, 0.5 * arrow_size))); + pts.push_back(xf.xform(Vector2(0, -0.5 * arrow_size))); + + Vector<Color> cols; + for (int i = 0; i < 3; i++) { + cols.push_back(draw_col); + } + + draw_primitive(pts, cols, Vector<Vector2>()); } void RayCast2D::force_raycast_update() { @@ -235,65 +251,57 @@ void RayCast2D::force_raycast_update() { } void RayCast2D::add_exception_rid(const RID &p_rid) { - exclude.insert(p_rid); } void RayCast2D::add_exception(const Object *p_object) { - ERR_FAIL_NULL(p_object); const CollisionObject2D *co = Object::cast_to<CollisionObject2D>(p_object); - if (!co) + if (!co) { return; + } add_exception_rid(co->get_rid()); } void RayCast2D::remove_exception_rid(const RID &p_rid) { - exclude.erase(p_rid); } void RayCast2D::remove_exception(const Object *p_object) { - ERR_FAIL_NULL(p_object); const CollisionObject2D *co = Object::cast_to<CollisionObject2D>(p_object); - if (!co) + if (!co) { return; + } remove_exception_rid(co->get_rid()); } void RayCast2D::clear_exceptions() { - exclude.clear(); } void RayCast2D::set_collide_with_areas(bool p_clip) { - collide_with_areas = p_clip; } bool RayCast2D::is_collide_with_areas_enabled() const { - return collide_with_areas; } void RayCast2D::set_collide_with_bodies(bool p_clip) { - collide_with_bodies = p_clip; } bool RayCast2D::is_collide_with_bodies_enabled() const { - return collide_with_bodies; } void RayCast2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &RayCast2D::set_enabled); ClassDB::bind_method(D_METHOD("is_enabled"), &RayCast2D::is_enabled); - ClassDB::bind_method(D_METHOD("set_cast_to", "local_point"), &RayCast2D::set_cast_to); - ClassDB::bind_method(D_METHOD("get_cast_to"), &RayCast2D::get_cast_to); + ClassDB::bind_method(D_METHOD("set_target_position", "local_point"), &RayCast2D::set_target_position); + ClassDB::bind_method(D_METHOD("get_target_position"), &RayCast2D::get_target_position); ClassDB::bind_method(D_METHOD("is_colliding"), &RayCast2D::is_colliding); ClassDB::bind_method(D_METHOD("force_raycast_update"), &RayCast2D::force_raycast_update); @@ -314,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); @@ -328,7 +336,7 @@ void RayCast2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "exclude_parent"), "set_exclude_parent_body", "get_exclude_parent_body"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "cast_to"), "set_cast_to", "get_cast_to"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "target_position"), "set_target_position", "get_target_position"); ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_mask", "get_collision_mask"); ADD_GROUP("Collide With", "collide_with"); @@ -337,14 +345,4 @@ void RayCast2D::_bind_methods() { } RayCast2D::RayCast2D() { - - enabled = false; - - collided = false; - against_shape = 0; - collision_mask = 1; - cast_to = Vector2(0, 50); - exclude_parent_body = true; - collide_with_bodies = true; - collide_with_areas = false; } diff --git a/scene/2d/ray_cast_2d.h b/scene/2d/ray_cast_2d.h index 20f05e7525..65b6e7899b 100644 --- a/scene/2d/ray_cast_2d.h +++ b/scene/2d/ray_cast_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -34,23 +34,24 @@ #include "scene/2d/node_2d.h" class RayCast2D : public Node2D { - GDCLASS(RayCast2D, Node2D); - bool enabled; - bool collided; + bool enabled = true; + bool collided = false; ObjectID against; - int against_shape; + int against_shape = 0; Vector2 collision_point; Vector2 collision_normal; Set<RID> exclude; - uint32_t collision_mask; - bool exclude_parent_body; + uint32_t collision_mask = 1; + bool exclude_parent_body = true; + + Vector2 target_position = Vector2(0, 50); - Vector2 cast_to; + bool collide_with_areas = false; + bool collide_with_bodies = true; - bool collide_with_areas; - bool collide_with_bodies; + void _draw_debug_shape(); protected: void _notification(int p_what); @@ -67,14 +68,14 @@ public: void set_enabled(bool p_enabled); bool is_enabled() const; - void set_cast_to(const Vector2 &p_point); - Vector2 get_cast_to() const; + void set_target_position(const Vector2 &p_point); + Vector2 get_target_position() const; void set_collision_mask(uint32_t p_mask); uint32_t get_collision_mask() const; - void set_collision_mask_bit(int p_bit, bool p_value); - bool get_collision_mask_bit(int p_bit) const; + void set_collision_mask_value(int p_layer_number, bool p_value); + bool get_collision_mask_value(int p_layer_number) const; void set_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 ec50f5f922..fe3e867424 100644 --- a/scene/2d/remote_transform_2d.cpp +++ b/scene/2d/remote_transform_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -29,14 +29,12 @@ /*************************************************************************/ #include "remote_transform_2d.h" -#include "scene/scene_string_names.h" void RemoteTransform2D::_update_cache() { - cache = ObjectID(); if (has_node(remote_node)) { Node *node = get_node(remote_node); - if (!node || this == node || node->is_a_parent_of(this) || this->is_a_parent_of(node)) { + if (!node || this == node || node->is_ancestor_of(this) || this->is_ancestor_of(node)) { return; } @@ -45,23 +43,25 @@ void RemoteTransform2D::_update_cache() { } void RemoteTransform2D::_update_remote() { - - if (!is_inside_tree()) + if (!is_inside_tree()) { return; + } - if (cache.is_null()) + if (cache.is_null()) { return; + } Node2D *n = Object::cast_to<Node2D>(ObjectDB::get_instance(cache)); - if (!n) + if (!n) { return; + } - if (!n->is_inside_tree()) + if (!n->is_inside_tree()) { return; + } //todo make faster if (use_global_coordinates) { - if (update_remote_position && update_remote_rotation && update_remote_scale) { n->set_global_transform(get_global_transform()); } else { @@ -69,21 +69,23 @@ void RemoteTransform2D::_update_remote() { Transform2D our_trans = get_global_transform(); Vector2 n_scale = n->get_scale(); - if (!update_remote_position) + if (!update_remote_position) { our_trans.set_origin(n_trans.get_origin()); - if (!update_remote_rotation) + } + if (!update_remote_rotation) { our_trans.set_rotation(n_trans.get_rotation()); + } n->set_global_transform(our_trans); - if (update_remote_scale) + if (update_remote_scale) { n->set_scale(get_global_scale()); - else + } else { n->set_scale(n_scale); + } } } else { - if (update_remote_position && update_remote_rotation && update_remote_scale) { n->set_transform(get_transform()); } else { @@ -91,36 +93,36 @@ void RemoteTransform2D::_update_remote() { Transform2D our_trans = get_transform(); Vector2 n_scale = n->get_scale(); - if (!update_remote_position) + if (!update_remote_position) { our_trans.set_origin(n_trans.get_origin()); - if (!update_remote_rotation) + } + if (!update_remote_rotation) { our_trans.set_rotation(n_trans.get_rotation()); + } n->set_transform(our_trans); - if (update_remote_scale) + if (update_remote_scale) { n->set_scale(get_scale()); - else + } else { n->set_scale(n_scale); + } } } } void RemoteTransform2D::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - _update_cache(); } break; case NOTIFICATION_TRANSFORM_CHANGED: { - if (!is_inside_tree()) + if (!is_inside_tree()) { break; + } if (cache.is_valid()) { - _update_remote(); } @@ -129,18 +131,16 @@ void RemoteTransform2D::_notification(int p_what) { } void RemoteTransform2D::set_remote_node(const NodePath &p_remote_node) { - remote_node = p_remote_node; if (is_inside_tree()) { _update_cache(); _update_remote(); } - update_configuration_warning(); + update_configuration_warnings(); } NodePath RemoteTransform2D::get_remote_node() const { - return remote_node; } @@ -184,17 +184,17 @@ void RemoteTransform2D::force_update_cache() { _update_cache(); } -String RemoteTransform2D::get_configuration_warning() const { +TypedArray<String> RemoteTransform2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); if (!has_node(remote_node) || !Object::cast_to<Node2D>(get_node(remote_node))) { - return TTR("Path property must point to a valid Node2D node to work."); + warnings.push_back(TTR("Path property must point to a valid Node2D node to work.")); } - return String(); + return warnings; } void RemoteTransform2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_remote_node", "path"), &RemoteTransform2D::set_remote_node); ClassDB::bind_method(D_METHOD("get_remote_node"), &RemoteTransform2D::get_remote_node); ClassDB::bind_method(D_METHOD("force_update_cache"), &RemoteTransform2D::force_update_cache); @@ -219,11 +219,5 @@ void RemoteTransform2D::_bind_methods() { } RemoteTransform2D::RemoteTransform2D() { - - use_global_coordinates = true; - update_remote_position = true; - update_remote_rotation = true; - update_remote_scale = true; - set_notify_transform(true); } diff --git a/scene/2d/remote_transform_2d.h b/scene/2d/remote_transform_2d.h index f5b949d10b..36fddb58c7 100644 --- a/scene/2d/remote_transform_2d.h +++ b/scene/2d/remote_transform_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -34,17 +34,16 @@ #include "scene/2d/node_2d.h" class RemoteTransform2D : public Node2D { - GDCLASS(RemoteTransform2D, Node2D); NodePath remote_node; ObjectID cache; - bool use_global_coordinates; - bool update_remote_position; - bool update_remote_rotation; - bool update_remote_scale; + bool use_global_coordinates = true; + bool update_remote_position = true; + bool update_remote_rotation = true; + bool update_remote_scale = true; void _update_remote(); void _update_cache(); @@ -71,7 +70,7 @@ public: void force_update_cache(); - virtual String get_configuration_warning() const; + TypedArray<String> get_configuration_warnings() const override; RemoteTransform2D(); }; diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp index 86c9ff6076..4bbbc3575d 100644 --- a/scene/2d/skeleton_2d.cpp +++ b/scene/2d/skeleton_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,18 +30,80 @@ #include "skeleton_2d.h" -void Bone2D::_notification(int p_what) { +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#include "editor/plugins/canvas_item_editor_plugin.h" +#endif //TOOLS_ENABLED + +bool Bone2D::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.begins_with("auto_calculate_length_and_angle")) { + set_autocalculate_length_and_angle(p_value); + } else if (path.begins_with("length")) { + set_length(p_value); + } else if (path.begins_with("bone_angle")) { + set_bone_angle(Math::deg2rad(real_t(p_value))); + } else if (path.begins_with("default_length")) { + set_length(p_value); + } + +#ifdef TOOLS_ENABLED + if (path.begins_with("editor_settings/show_bone_gizmo")) { + _editor_set_show_bone_gizmo(p_value); + } +#endif // TOOLS_ENABLED + + return true; +} + +bool Bone2D::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.begins_with("auto_calculate_length_and_angle")) { + r_ret = get_autocalculate_length_and_angle(); + } else if (path.begins_with("length")) { + r_ret = get_length(); + } else if (path.begins_with("bone_angle")) { + r_ret = Math::rad2deg(get_bone_angle()); + } else if (path.begins_with("default_length")) { + r_ret = get_length(); + } + +#ifdef TOOLS_ENABLED + if (path.begins_with("editor_settings/show_bone_gizmo")) { + r_ret = _editor_get_show_bone_gizmo(); + } +#endif // TOOLS_ENABLED + + return true; +} +void Bone2D::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back(PropertyInfo(Variant::BOOL, "auto_calculate_length_and_angle", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + if (!autocalculate_length_and_angle) { + p_list->push_back(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "1, 1024, 1", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, "bone_angle", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT)); + } + +#ifdef TOOLS_ENABLED + p_list->push_back(PropertyInfo(Variant::BOOL, "editor_settings/show_bone_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); +#endif // TOOLS_ENABLED +} + +void Bone2D::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { Node *parent = get_parent(); parent_bone = Object::cast_to<Bone2D>(parent); skeleton = nullptr; while (parent) { skeleton = Object::cast_to<Skeleton2D>(parent); - if (skeleton) + if (skeleton) { break; - if (!Object::cast_to<Bone2D>(parent)) + } + if (!Object::cast_to<Bone2D>(parent)) { break; //skeletons must be chained to Bone2Ds. + } parent = parent->get_parent(); } @@ -52,19 +114,54 @@ void Bone2D::_notification(int p_what) { skeleton->bones.push_back(bone); skeleton->_make_bone_setup_dirty(); } + + cache_transform = get_transform(); + copy_transform_to_cache = true; + +#ifdef TOOLS_ENABLED + // Only draw the gizmo in the editor! + if (Engine::get_singleton()->is_editor_hint() == false) { + return; + } + + update(); +#endif // TOOLS_ENABLED } - if (p_what == NOTIFICATION_LOCAL_TRANSFORM_CHANGED) { + + else if (p_what == NOTIFICATION_LOCAL_TRANSFORM_CHANGED) { if (skeleton) { skeleton->_make_transform_dirty(); } + if (copy_transform_to_cache) { + cache_transform = get_transform(); + } +#ifdef TOOLS_ENABLED + // Only draw the gizmo in the editor! + if (Engine::get_singleton()->is_editor_hint() == false) { + return; + } + + update(); + + if (get_parent()) { + Bone2D *parent_bone = Object::cast_to<Bone2D>(get_parent()); + if (parent_bone) { + parent_bone->update(); + } + } +#endif // TOOLS_ENABLED } - if (p_what == NOTIFICATION_MOVED_IN_PARENT) { + + else if (p_what == NOTIFICATION_MOVED_IN_PARENT) { if (skeleton) { skeleton->_make_bone_setup_dirty(); } + if (copy_transform_to_cache) { + cache_transform = get_transform(); + } } - if (p_what == NOTIFICATION_EXIT_TREE) { + else if (p_what == NOTIFICATION_EXIT_TREE) { if (skeleton) { for (int i = 0; i < skeleton->bones.size(); i++) { if (skeleton->bones[i].bone == this) { @@ -76,10 +173,201 @@ void Bone2D::_notification(int p_what) { skeleton = nullptr; } parent_bone = nullptr; + set_transform(cache_transform); + } + + else if (p_what == NOTIFICATION_READY) { + if (autocalculate_length_and_angle) { + calculate_length_and_rotation(); + } } +#ifdef TOOLS_ENABLED + else if (p_what == NOTIFICATION_EDITOR_PRE_SAVE || p_what == NOTIFICATION_EDITOR_POST_SAVE) { + Transform2D tmp_trans = get_transform(); + set_transform(cache_transform); + cache_transform = tmp_trans; + } + // Bone2D Editor gizmo drawing: +#ifndef _MSC_VER +#warning TODO Bone2D gizmo drawing needs to be moved to an editor plugin +#endif + else if (p_what == NOTIFICATION_DRAW) { + // Only draw the gizmo in the editor! + if (Engine::get_singleton()->is_editor_hint() == false) { + return; + } + + if (editor_gizmo_rid.is_null()) { + editor_gizmo_rid = RenderingServer::get_singleton()->canvas_item_create(); + RenderingServer::get_singleton()->canvas_item_set_parent(editor_gizmo_rid, get_canvas_item()); + RenderingServer::get_singleton()->canvas_item_set_z_as_relative_to_parent(editor_gizmo_rid, true); + RenderingServer::get_singleton()->canvas_item_set_z_index(editor_gizmo_rid, 10); + } + RenderingServer::get_singleton()->canvas_item_clear(editor_gizmo_rid); + + if (!_editor_show_bone_gizmo) { + return; + } + + // Undo scaling + Transform2D editor_gizmo_trans = Transform2D(); + editor_gizmo_trans.set_scale(Vector2(1, 1) / get_global_scale()); + RenderingServer::get_singleton()->canvas_item_set_transform(editor_gizmo_rid, editor_gizmo_trans); + + Color bone_color1 = EditorSettings::get_singleton()->get("editors/2d/bone_color1"); + Color bone_color2 = EditorSettings::get_singleton()->get("editors/2d/bone_color2"); + Color bone_ik_color = EditorSettings::get_singleton()->get("editors/2d/bone_ik_color"); + Color bone_outline_color = EditorSettings::get_singleton()->get("editors/2d/bone_outline_color"); + Color bone_selected_color = EditorSettings::get_singleton()->get("editors/2d/bone_selected_color"); + + bool Bone2D_found = false; + for (int i = 0; i < get_child_count(); i++) { + Bone2D *child_node = nullptr; + child_node = Object::cast_to<Bone2D>(get_child(i)); + if (!child_node) { + continue; + } + Bone2D_found = true; + + Vector<Vector2> bone_shape; + Vector<Vector2> bone_shape_outline; + + _editor_get_bone_shape(&bone_shape, &bone_shape_outline, child_node); + + Vector<Color> colors; + if (has_meta("_local_pose_override_enabled_")) { + colors.push_back(bone_ik_color); + colors.push_back(bone_ik_color); + colors.push_back(bone_ik_color); + colors.push_back(bone_ik_color); + } else { + colors.push_back(bone_color1); + colors.push_back(bone_color2); + colors.push_back(bone_color1); + colors.push_back(bone_color2); + } + + Vector<Color> outline_colors; + if (CanvasItemEditor::get_singleton()->editor_selection->is_selected(this)) { + outline_colors.push_back(bone_selected_color); + outline_colors.push_back(bone_selected_color); + outline_colors.push_back(bone_selected_color); + outline_colors.push_back(bone_selected_color); + outline_colors.push_back(bone_selected_color); + outline_colors.push_back(bone_selected_color); + } else { + outline_colors.push_back(bone_outline_color); + outline_colors.push_back(bone_outline_color); + outline_colors.push_back(bone_outline_color); + outline_colors.push_back(bone_outline_color); + outline_colors.push_back(bone_outline_color); + outline_colors.push_back(bone_outline_color); + } + + RenderingServer::get_singleton()->canvas_item_add_polygon(editor_gizmo_rid, bone_shape_outline, outline_colors); + RenderingServer::get_singleton()->canvas_item_add_polygon(editor_gizmo_rid, bone_shape, colors); + } + + if (!Bone2D_found) { + Vector<Vector2> bone_shape; + Vector<Vector2> bone_shape_outline; + + _editor_get_bone_shape(&bone_shape, &bone_shape_outline, nullptr); + + Vector<Color> colors; + if (has_meta("_local_pose_override_enabled_")) { + colors.push_back(bone_ik_color); + colors.push_back(bone_ik_color); + colors.push_back(bone_ik_color); + colors.push_back(bone_ik_color); + } else { + colors.push_back(bone_color1); + colors.push_back(bone_color2); + colors.push_back(bone_color1); + colors.push_back(bone_color2); + } + + Vector<Color> outline_colors; + if (CanvasItemEditor::get_singleton()->editor_selection->is_selected(this)) { + outline_colors.push_back(bone_selected_color); + outline_colors.push_back(bone_selected_color); + outline_colors.push_back(bone_selected_color); + outline_colors.push_back(bone_selected_color); + outline_colors.push_back(bone_selected_color); + outline_colors.push_back(bone_selected_color); + } else { + outline_colors.push_back(bone_outline_color); + outline_colors.push_back(bone_outline_color); + outline_colors.push_back(bone_outline_color); + outline_colors.push_back(bone_outline_color); + outline_colors.push_back(bone_outline_color); + outline_colors.push_back(bone_outline_color); + } + + RenderingServer::get_singleton()->canvas_item_add_polygon(editor_gizmo_rid, bone_shape_outline, outline_colors); + RenderingServer::get_singleton()->canvas_item_add_polygon(editor_gizmo_rid, bone_shape, colors); + } + } +#endif // TOOLS_ENALBED +} + +#ifdef TOOLS_ENABLED +bool Bone2D::_editor_get_bone_shape(Vector<Vector2> *p_shape, Vector<Vector2> *p_outline_shape, Bone2D *p_other_bone) { + int bone_width = EditorSettings::get_singleton()->get("editors/2d/bone_width"); + int bone_outline_width = EditorSettings::get_singleton()->get("editors/2d/bone_outline_size"); + + if (!is_inside_tree()) { + return false; //may have been removed + } + if (!p_other_bone && length <= 0) { + return false; + } + + Vector2 rel; + if (p_other_bone) { + 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 { + 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 + } + + Vector2 relt = rel.rotated(Math_PI * 0.5).normalized() * bone_width; + Vector2 reln = rel.normalized(); + Vector2 reltn = relt.normalized(); + + if (p_shape) { + p_shape->clear(); + p_shape->push_back(Vector2(0, 0)); + p_shape->push_back(rel * 0.2 + relt); + p_shape->push_back(rel); + p_shape->push_back(rel * 0.2 - relt); + } + + if (p_outline_shape) { + p_outline_shape->clear(); + p_outline_shape->push_back((-reln - reltn) * bone_outline_width); + p_outline_shape->push_back((-reln + reltn) * bone_outline_width); + p_outline_shape->push_back(rel * 0.2 + relt + reltn * bone_outline_width); + p_outline_shape->push_back(rel + (reln + reltn) * bone_outline_width); + p_outline_shape->push_back(rel + (reln - reltn) * bone_outline_width); + p_outline_shape->push_back(rel * 0.2 - relt - reltn * bone_outline_width); + } + return true; } -void Bone2D::_bind_methods() { +void Bone2D::_editor_set_show_bone_gizmo(bool p_show_gizmo) { + _editor_show_bone_gizmo = p_show_gizmo; + update(); +} + +bool Bone2D::_editor_get_show_bone_gizmo() const { + return _editor_show_bone_gizmo; +} +#endif // TOOLS_ENABLED + +void Bone2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_rest", "rest"), &Bone2D::set_rest); ClassDB::bind_method(D_METHOD("get_rest"), &Bone2D::get_rest); ClassDB::bind_method(D_METHOD("apply_rest"), &Bone2D::apply_rest); @@ -89,16 +377,23 @@ void Bone2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_default_length", "default_length"), &Bone2D::set_default_length); ClassDB::bind_method(D_METHOD("get_default_length"), &Bone2D::get_default_length); + ClassDB::bind_method(D_METHOD("set_autocalculate_length_and_angle", "auto_calculate"), &Bone2D::set_autocalculate_length_and_angle); + ClassDB::bind_method(D_METHOD("get_autocalculate_length_and_angle"), &Bone2D::get_autocalculate_length_and_angle); + ClassDB::bind_method(D_METHOD("set_length", "length"), &Bone2D::set_length); + ClassDB::bind_method(D_METHOD("get_length"), &Bone2D::get_length); + ClassDB::bind_method(D_METHOD("set_bone_angle", "angle"), &Bone2D::set_bone_angle); + ClassDB::bind_method(D_METHOD("get_bone_angle"), &Bone2D::get_bone_angle); + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "rest"), "set_rest", "get_rest"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "default_length", PROPERTY_HINT_RANGE, "1,1024,1"), "set_default_length", "get_default_length"); } void Bone2D::set_rest(const Transform2D &p_rest) { rest = p_rest; - if (skeleton) + if (skeleton) { skeleton->_make_bone_setup_dirty(); + } - update_configuration_warning(); + update_configuration_warnings(); } Transform2D Bone2D::get_rest() const { @@ -106,7 +401,6 @@ Transform2D Bone2D::get_rest() const { } Transform2D Bone2D::get_skeleton_rest() const { - if (parent_bone) { return parent_bone->get_skeleton_rest() * rest; } else { @@ -118,13 +412,14 @@ void Bone2D::apply_rest() { set_transform(rest); } -void Bone2D::set_default_length(float p_length) { - - default_length = 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 { - return default_length; +real_t Bone2D::get_default_length() const { + WARN_DEPRECATED_MSG("get_default_length is deprecated. Please use get_length instead!"); + return get_length(); } int Bone2D::get_index_in_skeleton() const { @@ -132,63 +427,158 @@ int Bone2D::get_index_in_skeleton() const { skeleton->_update_bone_setup(); return skeleton_index; } -String Bone2D::get_configuration_warning() const { - String warning = Node2D::get_configuration_warning(); +TypedArray<String> Bone2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); if (!skeleton) { - if (warning != String()) { - warning += "\n\n"; - } if (parent_bone) { - warning += TTR("This Bone2D chain should end at a Skeleton2D node."); + warnings.push_back(TTR("This Bone2D chain should end at a Skeleton2D node.")); } else { - warning += TTR("A Bone2D only works with a Skeleton2D or another Bone2D as parent node."); + warnings.push_back(TTR("A Bone2D only works with a Skeleton2D or another Bone2D as parent node.")); } } if (rest == Transform2D(0, 0, 0, 0, 0, 0)) { - if (warning != String()) { - warning += "\n\n"; + warnings.push_back(TTR("This bone lacks a proper REST pose. Go to the Skeleton2D node and set one.")); + } + + return warnings; +} + +void Bone2D::calculate_length_and_rotation() { + // if there is at least a single child Bone2D node, we can calculate + // the length and direction. We will always just use the first Bone2D for this. + bool calculated = false; + int child_count = get_child_count(); + if (child_count > 0) { + 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_position()); + length = child_local_pos.length(); + bone_angle = Math::atan2(child_local_pos.normalized().y, child_local_pos.normalized().x); + calculated = true; + break; + } } - warning += TTR("This bone lacks a proper REST pose. Go to the Skeleton2D node and set one."); } + if (calculated) { + return; // Finished! + } else { + WARN_PRINT("No Bone2D children of node " + get_name() + ". Cannot calculate bone length or angle reliably.\nUsing transform rotation for bone angle"); + bone_angle = get_transform().get_rotation(); + return; + } +} - return warning; +void Bone2D::set_autocalculate_length_and_angle(bool p_autocalculate) { + autocalculate_length_and_angle = p_autocalculate; + if (autocalculate_length_and_angle) { + calculate_length_and_rotation(); + } + notify_property_list_changed(); +} + +bool Bone2D::get_autocalculate_length_and_angle() const { + return autocalculate_length_and_angle; +} + +void Bone2D::set_length(real_t p_length) { + length = p_length; + +#ifdef TOOLS_ENABLED + update(); +#endif // TOOLS_ENABLED +} + +real_t Bone2D::get_length() const { + return length; +} + +void Bone2D::set_bone_angle(real_t p_angle) { + bone_angle = p_angle; + +#ifdef TOOLS_ENABLED + update(); +#endif // TOOLS_ENABLED +} + +real_t Bone2D::get_bone_angle() const { + return bone_angle; } Bone2D::Bone2D() { skeleton = nullptr; parent_bone = nullptr; skeleton_index = -1; - default_length = 16; + length = 16; + bone_angle = 0; + autocalculate_length_and_angle = true; set_notify_local_transform(true); //this is a clever hack so the bone knows no rest has been set yet, allowing to show an error. for (int i = 0; i < 3; i++) { rest[i] = Vector2(0, 0); } + copy_transform_to_cache = true; +} + +Bone2D::~Bone2D() { +#ifdef TOOLS_ENABLED + if (!editor_gizmo_rid.is_null()) { + RenderingServer::get_singleton()->free(editor_gizmo_rid); + } +#endif // TOOLS_ENABLED } ////////////////////////////////////// -void Skeleton2D::_make_bone_setup_dirty() { +bool Skeleton2D::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.begins_with("modification_stack")) { + set_modification_stack(p_value); + return true; + } + return true; +} - if (bone_setup_dirty) +bool Skeleton2D::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.begins_with("modification_stack")) { + r_ret = get_modification_stack(); + return true; + } + return true; +} + +void Skeleton2D::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back( + PropertyInfo(Variant::OBJECT, "modification_stack", + PROPERTY_HINT_RESOURCE_TYPE, + "SkeletonModificationStack2D", + PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DEFERRED_SET_RESOURCE | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE)); +} + +void Skeleton2D::_make_bone_setup_dirty() { + if (bone_setup_dirty) { return; + } bone_setup_dirty = true; if (is_inside_tree()) { - call_deferred("_update_bone_setup"); + call_deferred(SNAME("_update_bone_setup")); } } void Skeleton2D::_update_bone_setup() { - - if (!bone_setup_dirty) + if (!bone_setup_dirty) { return; + } bone_setup_dirty = false; - RS::get_singleton()->skeleton_allocate(skeleton, bones.size(), true); + RS::get_singleton()->skeleton_allocate_data(skeleton, bones.size(), true); - bones.sort(); //sorty so they are always in the same order/index + bones.sort(); //sorting so that they are always in the same order/index for (int i = 0; i < bones.size(); i++) { bones.write[i].rest_inverse = bones[i].bone->get_skeleton_rest().affine_inverse(); //bind pose @@ -199,36 +589,37 @@ void Skeleton2D::_update_bone_setup() { } else { bones.write[i].parent_index = -1; } + + bones.write[i].local_pose_override = bones[i].bone->get_skeleton_rest(); } transform_dirty = true; _update_transform(); - emit_signal("bone_setup_changed"); + emit_signal(SNAME("bone_setup_changed")); } void Skeleton2D::_make_transform_dirty() { - - if (transform_dirty) + if (transform_dirty) { return; + } transform_dirty = true; if (is_inside_tree()) { - call_deferred("_update_transform"); + call_deferred(SNAME("_update_transform")); } } void Skeleton2D::_update_transform() { - if (bone_setup_dirty) { _update_bone_setup(); return; //above will update transform anyway } - if (!transform_dirty) + if (!transform_dirty) { return; + } transform_dirty = false; for (int i = 0; i < bones.size(); i++) { - ERR_CONTINUE(bones[i].parent_index >= i); if (bones[i].parent_index >= 0) { bones.write[i].accum_transform = bones[bones[i].parent_index].accum_transform * bones[i].bone->get_transform(); @@ -238,14 +629,12 @@ void Skeleton2D::_update_transform() { } for (int i = 0; i < bones.size(); i++) { - Transform2D final_xform = bones[i].accum_transform * bones[i].rest_inverse; RS::get_singleton()->skeleton_bone_set_transform_2d(skeleton, i, final_xform); } } int Skeleton2D::get_bone_count() const { - ERR_FAIL_COND_V(!is_inside_tree(), 0); if (bone_setup_dirty) { @@ -256,7 +645,6 @@ int Skeleton2D::get_bone_count() const { } Bone2D *Skeleton2D::get_bone(int p_idx) { - ERR_FAIL_COND_V(!is_inside_tree(), nullptr); ERR_FAIL_INDEX_V(p_idx, bones.size(), nullptr); @@ -264,27 +652,129 @@ Bone2D *Skeleton2D::get_bone(int p_idx) { } void Skeleton2D::_notification(int p_what) { - if (p_what == NOTIFICATION_READY) { - - if (bone_setup_dirty) + if (bone_setup_dirty) { _update_bone_setup(); - if (transform_dirty) + } + if (transform_dirty) { _update_transform(); - + } request_ready(); } if (p_what == NOTIFICATION_TRANSFORM_CHANGED) { RS::get_singleton()->skeleton_set_base_transform_2d(skeleton, get_global_transform()); + } else if (p_what == NOTIFICATION_INTERNAL_PROCESS) { + if (modification_stack.is_valid()) { + execute_modifications(get_process_delta_time(), SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_process); + } + } else if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { + if (modification_stack.is_valid()) { + execute_modifications(get_physics_process_delta_time(), SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_physics_process); + } } +#ifdef TOOLS_ENABLED + else if (p_what == NOTIFICATION_DRAW) { + if (Engine::get_singleton()->is_editor_hint()) { + if (modification_stack.is_valid()) { + modification_stack->draw_editor_gizmos(); + } + } + } +#endif // TOOLS_ENABLED } RID Skeleton2D::get_skeleton() const { return skeleton; } -void Skeleton2D::_bind_methods() { +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; + bones.write[p_bone_idx].local_pose_override_persistent = p_persistent; +} + +Transform2D Skeleton2D::get_bone_local_pose_override(int p_bone_idx) { + ERR_FAIL_INDEX_V_MSG(p_bone_idx, bones.size(), Transform2D(), "Bone index is out of range!"); + return bones[p_bone_idx].local_pose_override; +} + +void Skeleton2D::set_modification_stack(Ref<SkeletonModificationStack2D> p_stack) { + if (modification_stack.is_valid()) { + modification_stack->is_setup = false; + modification_stack->set_skeleton(nullptr); + + set_process_internal(false); + set_physics_process_internal(false); + } + modification_stack = p_stack; + if (modification_stack.is_valid()) { + modification_stack->set_skeleton(this); + modification_stack->setup(); + + set_process_internal(true); + set_physics_process_internal(true); + +#ifdef TOOLS_ENABLED + modification_stack->set_editor_gizmos_dirty(true); +#endif // TOOLS_ENABLED + } +} + +Ref<SkeletonModificationStack2D> Skeleton2D::get_modification_stack() const { + return modification_stack; +} + +void Skeleton2D::execute_modifications(real_t p_delta, int p_execution_mode) { + if (!modification_stack.is_valid()) { + return; + } + + // Do not cache the transform changes caused by the modifications! + for (int i = 0; i < bones.size(); i++) { + bones[i].bone->copy_transform_to_cache = false; + } + + if (modification_stack->skeleton != this) { + modification_stack->set_skeleton(this); + } + + modification_stack->execute(p_delta, p_execution_mode); + + // Only apply the local pose override on _process. Otherwise, just calculate the local_pose_override and reset the transform. + if (p_execution_mode == SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_process) { + for (int i = 0; i < bones.size(); i++) { + if (bones[i].local_pose_override_amount > 0) { + bones[i].bone->set_meta("_local_pose_override_enabled_", true); + + Transform2D final_trans = bones[i].bone->cache_transform; + final_trans = final_trans.interpolate_with(bones[i].local_pose_override, bones[i].local_pose_override_amount); + bones[i].bone->set_transform(final_trans); + bones[i].bone->propagate_call("force_update_transform"); + + if (bones[i].local_pose_override_persistent) { + bones.write[i].local_pose_override_amount = 0.0; + } + } else { + // TODO: see if there is a way to undo the override without having to resort to setting every bone's transform. + bones[i].bone->remove_meta("_local_pose_override_enabled_"); + bones[i].bone->set_transform(bones[i].bone->cache_transform); + } + } + } + + // Cache any future transform changes + for (int i = 0; i < bones.size(); i++) { + bones[i].bone->copy_transform_to_cache = true; + } + +#ifdef TOOLS_ENABLED + modification_stack->set_editor_gizmos_dirty(true); +#endif // TOOLS_ENABLED +} + +void Skeleton2D::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_bone_setup"), &Skeleton2D::_update_bone_setup); ClassDB::bind_method(D_METHOD("_update_transform"), &Skeleton2D::_update_transform); @@ -293,18 +783,21 @@ void Skeleton2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_skeleton"), &Skeleton2D::get_skeleton); + ClassDB::bind_method(D_METHOD("set_modification_stack", "modification_stack"), &Skeleton2D::set_modification_stack); + ClassDB::bind_method(D_METHOD("get_modification_stack"), &Skeleton2D::get_modification_stack); + ClassDB::bind_method(D_METHOD("execute_modifications", "delta", "execution_mode"), &Skeleton2D::execute_modifications); + + ClassDB::bind_method(D_METHOD("set_bone_local_pose_override", "bone_idx", "override_pose", "strength", "persistent"), &Skeleton2D::set_bone_local_pose_override); + ClassDB::bind_method(D_METHOD("get_bone_local_pose_override", "bone_idx"), &Skeleton2D::get_bone_local_pose_override); + ADD_SIGNAL(MethodInfo("bone_setup_changed")); } Skeleton2D::Skeleton2D() { - bone_setup_dirty = true; - transform_dirty = true; - skeleton = RS::get_singleton()->skeleton_create(); set_notify_transform(true); } Skeleton2D::~Skeleton2D() { - RS::get_singleton()->free(skeleton); } diff --git a/scene/2d/skeleton_2d.h b/scene/2d/skeleton_2d.h index c6bee8d92b..56fd0e8504 100644 --- a/scene/2d/skeleton_2d.h +++ b/scene/2d/skeleton_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,6 +32,7 @@ #define SKELETON_2D_H #include "scene/2d/node_2d.h" +#include "scene/resources/skeleton_modification_2d.h" class Skeleton2D; @@ -43,33 +44,65 @@ class Bone2D : public Node2D { friend class AnimatedValuesBackup; #endif - Bone2D *parent_bone; - Skeleton2D *skeleton; + Bone2D *parent_bone = nullptr; + Skeleton2D *skeleton = nullptr; Transform2D rest; - float default_length; - int skeleton_index; + bool autocalculate_length_and_angle = true; + real_t length = 16; + real_t bone_angle = 0; + + int skeleton_index = -1; + + void calculate_length_and_rotation(); + +#ifdef TOOLS_ENABLED + RID editor_gizmo_rid; + bool _editor_get_bone_shape(Vector<Vector2> *p_shape, Vector<Vector2> *p_outline_shape, Bone2D *p_other_bone); + bool _editor_show_bone_gizmo = true; +#endif // TOOLS ENABLED protected: void _notification(int p_what); static void _bind_methods(); + bool _set(const StringName &p_path, const Variant &p_value); + bool _get(const StringName &p_path, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; public: + Transform2D cache_transform; + bool copy_transform_to_cache = true; + void set_rest(const Transform2D &p_rest); Transform2D get_rest() const; void apply_rest(); Transform2D get_skeleton_rest() const; - String get_configuration_warning() const; + TypedArray<String> get_configuration_warnings() const override; + + void set_default_length(real_t p_length); + real_t get_default_length() const; - void set_default_length(float p_length); - float get_default_length() const; + void set_autocalculate_length_and_angle(bool p_autocalculate); + bool get_autocalculate_length_and_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; +#ifdef TOOLS_ENABLED + void _editor_set_show_bone_gizmo(bool p_show_gizmo); + bool _editor_get_show_bone_gizmo() const; +#endif // TOOLS_ENABLED + Bone2D(); + ~Bone2D(); }; +class SkeletonModificationStack2D; + class Skeleton2D : public Node2D { GDCLASS(Skeleton2D, Node2D); @@ -82,33 +115,51 @@ class Skeleton2D : public Node2D { bool operator<(const Bone &p_bone) const { return p_bone.bone->is_greater_than(bone); } - Bone2D *bone; - int parent_index; + Bone2D *bone = nullptr; + int parent_index = 0; Transform2D accum_transform; Transform2D rest_inverse; + + //Transform2D local_pose_cache; + Transform2D local_pose_override; + real_t local_pose_override_amount = 0; + bool local_pose_override_persistent = false; }; Vector<Bone> bones; - bool bone_setup_dirty; + bool bone_setup_dirty = true; void _make_bone_setup_dirty(); void _update_bone_setup(); - bool transform_dirty; + bool transform_dirty = true; void _make_transform_dirty(); void _update_transform(); RID skeleton; + Ref<SkeletonModificationStack2D> modification_stack; + protected: void _notification(int p_what); static void _bind_methods(); + bool _set(const StringName &p_path, const Variant &p_value); + bool _get(const StringName &p_path, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; public: int get_bone_count() const; Bone2D *get_bone(int p_idx); RID get_skeleton() const; + + 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(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 df8859bd9a..5761f19a53 100644 --- a/scene/2d/sprite_2d.cpp +++ b/scene/2d/sprite_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -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" @@ -61,7 +60,6 @@ bool Sprite2D::_edit_use_pivot() const { } bool Sprite2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { - return is_pixel_opaque(p_point); } @@ -78,15 +76,14 @@ Rect2 Sprite2D::get_anchorable_rect() const { return get_rect(); } -void Sprite2D::_get_rects(Rect2 &r_src_rect, Rect2 &r_dst_rect, bool &r_filter_clip) const { - +void Sprite2D::_get_rects(Rect2 &r_src_rect, Rect2 &r_dst_rect, bool &r_filter_clip_enabled) const { Rect2 base_rect; - if (region) { - r_filter_clip = region_filter_clip; + if (region_enabled) { + r_filter_clip_enabled = region_filter_clip_enabled; base_rect = region_rect; } else { - r_filter_clip = false; + r_filter_clip_enabled = false; base_rect = Rect2(0, 0, texture->get_width(), texture->get_height()); } @@ -98,28 +95,30 @@ void Sprite2D::_get_rects(Rect2 &r_src_rect, Rect2 &r_dst_rect, bool &r_filter_c r_src_rect.position = base_rect.position + frame_offset; Point2 dest_offset = offset; - if (centered) + if (centered) { dest_offset -= frame_size / 2; - if (Engine::get_singleton()->get_use_pixel_snap()) { + } + + if (get_viewport() && get_viewport()->is_snap_2d_transforms_to_pixel_enabled()) { dest_offset = dest_offset.floor(); } r_dst_rect = Rect2(dest_offset, frame_size); - if (hflip) + if (hflip) { r_dst_rect.size.x = -r_dst_rect.size.x; - if (vflip) + } + if (vflip) { r_dst_rect.size.y = -r_dst_rect.size.y; + } } void Sprite2D::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_DRAW: { - - if (texture.is_null()) + if (texture.is_null()) { return; + } RID ci = get_canvas_item(); @@ -129,240 +128,191 @@ void Sprite2D::_notification(int p_what) { */ Rect2 src_rect, dst_rect; - bool filter_clip; - _get_rects(src_rect, dst_rect, filter_clip); - texture->draw_rect_region(ci, dst_rect, src_rect, Color(1, 1, 1), false, normal_map, specular, Color(specular_color.r, specular_color.g, specular_color.b, shininess), RS::CANVAS_ITEM_TEXTURE_FILTER_DEFAULT, RS::CANVAS_ITEM_TEXTURE_REPEAT_DEFAULT, filter_clip); + bool filter_clip_enabled; + _get_rects(src_rect, dst_rect, filter_clip_enabled); + texture->draw_rect_region(ci, dst_rect, src_rect, Color(1, 1, 1), false, filter_clip_enabled); } break; } } void Sprite2D::set_texture(const Ref<Texture2D> &p_texture) { - - if (p_texture == texture) + if (p_texture == texture) { return; + } - if (texture.is_valid()) + if (texture.is_valid()) { texture->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Sprite2D::_texture_changed)); + } texture = p_texture; - if (texture.is_valid()) + if (texture.is_valid()) { texture->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Sprite2D::_texture_changed)); + } update(); - emit_signal("texture_changed"); + emit_signal(SceneStringNames::get_singleton()->texture_changed); item_rect_changed(); - _change_notify("texture"); -} - -void Sprite2D::set_normal_map(const Ref<Texture2D> &p_texture) { - - normal_map = p_texture; - update(); -} - -Ref<Texture2D> Sprite2D::get_normal_map() const { - - return normal_map; -} - -void Sprite2D::set_specular_map(const Ref<Texture2D> &p_texture) { - - specular = p_texture; - update(); -} - -Ref<Texture2D> Sprite2D::get_specular_map() const { - - return specular; -} - -void Sprite2D::set_specular_color(const Color &p_color) { - specular_color = p_color; - update(); -} - -Color Sprite2D::get_specular_color() const { - return specular_color; -} - -void Sprite2D::set_shininess(float p_shininess) { - shininess = CLAMP(p_shininess, 0.0, 1.0); - update(); -} - -float Sprite2D::get_shininess() const { - return shininess; } Ref<Texture2D> Sprite2D::get_texture() const { - return texture; } void Sprite2D::set_centered(bool p_center) { - centered = p_center; update(); item_rect_changed(); } bool Sprite2D::is_centered() const { - return centered; } void Sprite2D::set_offset(const Point2 &p_offset) { - offset = p_offset; update(); item_rect_changed(); - _change_notify("offset"); } -Point2 Sprite2D::get_offset() const { +Point2 Sprite2D::get_offset() const { return offset; } void Sprite2D::set_flip_h(bool p_flip) { - hflip = p_flip; update(); } -bool Sprite2D::is_flipped_h() const { +bool Sprite2D::is_flipped_h() const { return hflip; } void Sprite2D::set_flip_v(bool p_flip) { - vflip = p_flip; update(); } -bool Sprite2D::is_flipped_v() const { +bool Sprite2D::is_flipped_v() const { return vflip; } -void Sprite2D::set_region(bool p_region) { - - if (p_region == region) +void Sprite2D::set_region_enabled(bool p_region_enabled) { + if (p_region_enabled == region_enabled) { return; + } - region = p_region; + region_enabled = p_region_enabled; update(); + notify_property_list_changed(); } -bool Sprite2D::is_region() const { - - return region; +bool Sprite2D::is_region_enabled() const { + return region_enabled; } void Sprite2D::set_region_rect(const Rect2 &p_region_rect) { - - if (region_rect == p_region_rect) + if (region_rect == p_region_rect) { return; + } region_rect = p_region_rect; - if (region) + if (region_enabled) { item_rect_changed(); - - _change_notify("region_rect"); + } } Rect2 Sprite2D::get_region_rect() const { - return region_rect; } -void Sprite2D::set_region_filter_clip(bool p_enable) { - region_filter_clip = p_enable; +void Sprite2D::set_region_filter_clip_enabled(bool p_region_filter_clip_enabled) { + region_filter_clip_enabled = p_region_filter_clip_enabled; update(); } bool Sprite2D::is_region_filter_clip_enabled() const { - return region_filter_clip; + return region_filter_clip_enabled; } void Sprite2D::set_frame(int p_frame) { - ERR_FAIL_INDEX(p_frame, vframes * hframes); - if (frame != p_frame) + if (frame != p_frame) { item_rect_changed(); + } frame = p_frame; - _change_notify("frame"); - _change_notify("frame_coords"); emit_signal(SceneStringNames::get_singleton()->frame_changed); } int Sprite2D::get_frame() const { - return frame; } -void Sprite2D::set_frame_coords(const Vector2 &p_coord) { - ERR_FAIL_INDEX(int(p_coord.x), hframes); - ERR_FAIL_INDEX(int(p_coord.y), vframes); +void Sprite2D::set_frame_coords(const Vector2i &p_coord) { + ERR_FAIL_INDEX(p_coord.x, hframes); + ERR_FAIL_INDEX(p_coord.y, vframes); - set_frame(int(p_coord.y) * hframes + int(p_coord.x)); + set_frame(p_coord.y * hframes + p_coord.x); } -Vector2 Sprite2D::get_frame_coords() const { - return Vector2(frame % hframes, frame / hframes); +Vector2i Sprite2D::get_frame_coords() const { + return Vector2i(frame % hframes, frame / hframes); } void Sprite2D::set_vframes(int p_amount) { - ERR_FAIL_COND_MSG(p_amount < 1, "Amount of vframes cannot be smaller than 1."); vframes = p_amount; update(); item_rect_changed(); - _change_notify(); + notify_property_list_changed(); } -int Sprite2D::get_vframes() const { +int Sprite2D::get_vframes() const { return vframes; } void Sprite2D::set_hframes(int p_amount) { - ERR_FAIL_COND_MSG(p_amount < 1, "Amount of hframes cannot be smaller than 1."); hframes = p_amount; update(); item_rect_changed(); - _change_notify(); + notify_property_list_changed(); } -int Sprite2D::get_hframes() const { +int Sprite2D::get_hframes() const { return hframes; } bool Sprite2D::is_pixel_opaque(const Point2 &p_point) const { - - if (texture.is_null()) + if (texture.is_null()) { return false; + } - if (texture->get_size().width == 0 || texture->get_size().height == 0) + if (texture->get_size().width == 0 || texture->get_size().height == 0) { return false; + } Rect2 src_rect, dst_rect; - bool filter_clip; - _get_rects(src_rect, dst_rect, filter_clip); + bool filter_clip_enabled; + _get_rects(src_rect, dst_rect, filter_clip_enabled); dst_rect.size = dst_rect.size.abs(); - if (!dst_rect.has_point(p_point)) + if (!dst_rect.has_point(p_point)) { return false; + } Vector2 q = (p_point - dst_rect.position) / dst_rect.size; - if (hflip) + if (hflip) { q.x = 1.0f - q.x; - if (vflip) + } + if (vflip) { q.y = 1.0f - q.y; + } q = q * src_rect.size + src_rect.position; #ifndef _MSC_VER #warning this need to be obtained from CanvasItem new repeat mode (but it needs to guess it from hierarchy, need to add a function for that) @@ -393,13 +343,13 @@ bool Sprite2D::is_pixel_opaque(const Point2 &p_point) const { } Rect2 Sprite2D::get_rect() const { - - if (texture.is_null()) + if (texture.is_null()) { return Rect2(0, 0, 1, 1); + } Size2i s; - if (region) { + if (region_enabled) { s = region_rect.size; } else { s = texture->get_size(); @@ -408,17 +358,22 @@ Rect2 Sprite2D::get_rect() const { s = s / Point2(hframes, vframes); Point2 ofs = offset; - if (centered) + if (centered) { ofs -= Size2(s) / 2; + } - if (s == Size2(0, 0)) + if (get_viewport() && get_viewport()->is_snap_2d_transforms_to_pixel_enabled()) { + ofs = ofs.floor(); + } + + if (s == Size2(0, 0)) { s = Size2(1, 1); + } return Rect2(ofs, s); } void Sprite2D::_validate_property(PropertyInfo &property) const { - if (property.name == "frame") { property.hint = PROPERTY_HINT_RANGE; property.hint_string = "0," + itos(vframes * hframes - 1) + ",1"; @@ -428,10 +383,13 @@ void Sprite2D::_validate_property(PropertyInfo &property) const { if (property.name == "frame_coords") { property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS; } + + if (!region_enabled && (property.name == "region_rect" || property.name == "region_filter_clip")) { + property.usage = PROPERTY_USAGE_NOEDITOR; + } } void Sprite2D::_texture_changed() { - // Changes to the texture need to trigger an update to make // the editor redraw the sprite with the updated texture. if (texture.is_valid()) { @@ -440,22 +398,9 @@ void Sprite2D::_texture_changed() { } void Sprite2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_texture", "texture"), &Sprite2D::set_texture); ClassDB::bind_method(D_METHOD("get_texture"), &Sprite2D::get_texture); - ClassDB::bind_method(D_METHOD("set_normal_map", "normal_map"), &Sprite2D::set_normal_map); - ClassDB::bind_method(D_METHOD("get_normal_map"), &Sprite2D::get_normal_map); - - ClassDB::bind_method(D_METHOD("set_specular_map", "specular_map"), &Sprite2D::set_specular_map); - ClassDB::bind_method(D_METHOD("get_specular_map"), &Sprite2D::get_specular_map); - - ClassDB::bind_method(D_METHOD("set_specular_color", "specular_color"), &Sprite2D::set_specular_color); - ClassDB::bind_method(D_METHOD("get_specular_color"), &Sprite2D::get_specular_color); - - ClassDB::bind_method(D_METHOD("set_shininess", "shininess"), &Sprite2D::set_shininess); - ClassDB::bind_method(D_METHOD("get_shininess"), &Sprite2D::get_shininess); - ClassDB::bind_method(D_METHOD("set_centered", "centered"), &Sprite2D::set_centered); ClassDB::bind_method(D_METHOD("is_centered"), &Sprite2D::is_centered); @@ -468,15 +413,15 @@ void Sprite2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_flip_v", "flip_v"), &Sprite2D::set_flip_v); ClassDB::bind_method(D_METHOD("is_flipped_v"), &Sprite2D::is_flipped_v); - ClassDB::bind_method(D_METHOD("set_region", "enabled"), &Sprite2D::set_region); - ClassDB::bind_method(D_METHOD("is_region"), &Sprite2D::is_region); + ClassDB::bind_method(D_METHOD("set_region_enabled", "enabled"), &Sprite2D::set_region_enabled); + ClassDB::bind_method(D_METHOD("is_region_enabled"), &Sprite2D::is_region_enabled); ClassDB::bind_method(D_METHOD("is_pixel_opaque", "pos"), &Sprite2D::is_pixel_opaque); ClassDB::bind_method(D_METHOD("set_region_rect", "rect"), &Sprite2D::set_region_rect); ClassDB::bind_method(D_METHOD("get_region_rect"), &Sprite2D::get_region_rect); - ClassDB::bind_method(D_METHOD("set_region_filter_clip", "enabled"), &Sprite2D::set_region_filter_clip); + ClassDB::bind_method(D_METHOD("set_region_filter_clip_enabled", "enabled"), &Sprite2D::set_region_filter_clip_enabled); ClassDB::bind_method(D_METHOD("is_region_filter_clip_enabled"), &Sprite2D::is_region_filter_clip_enabled); ClassDB::bind_method(D_METHOD("set_frame", "frame"), &Sprite2D::set_frame); @@ -497,42 +442,24 @@ void Sprite2D::_bind_methods() { ADD_SIGNAL(MethodInfo("texture_changed")); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture"); - ADD_GROUP("Lighting", ""); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "normal_map", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_normal_map", "get_normal_map"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "specular_map", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_specular_map", "get_specular_map"); - ADD_PROPERTY(PropertyInfo(Variant::COLOR, "specular_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_specular_color", "get_specular_color"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "shininess", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_shininess", "get_shininess"); ADD_GROUP("Offset", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "centered"), "set_centered", "is_centered"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_offset", "get_offset"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_h"), "set_flip_h", "is_flipped_h"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_v"), "set_flip_v", "is_flipped_v"); ADD_GROUP("Animation", ""); - ADD_PROPERTY(PropertyInfo(Variant::INT, "vframes", PROPERTY_HINT_RANGE, "1,16384,1"), "set_vframes", "get_vframes"); ADD_PROPERTY(PropertyInfo(Variant::INT, "hframes", PROPERTY_HINT_RANGE, "1,16384,1"), "set_hframes", "get_hframes"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "vframes", PROPERTY_HINT_RANGE, "1,16384,1"), "set_vframes", "get_vframes"); ADD_PROPERTY(PropertyInfo(Variant::INT, "frame"), "set_frame", "get_frame"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "frame_coords", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_frame_coords", "get_frame_coords"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "frame_coords", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_frame_coords", "get_frame_coords"); ADD_GROUP("Region", "region_"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "region_enabled"), "set_region", "is_region"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "region_enabled"), "set_region_enabled", "is_region_enabled"); ADD_PROPERTY(PropertyInfo(Variant::RECT2, "region_rect"), "set_region_rect", "get_region_rect"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "region_filter_clip"), "set_region_filter_clip", "is_region_filter_clip_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "region_filter_clip_enabled"), "set_region_filter_clip_enabled", "is_region_filter_clip_enabled"); } Sprite2D::Sprite2D() { - - centered = true; - hflip = false; - vflip = false; - region = false; - region_filter_clip = false; - shininess = 1.0; - specular_color = Color(1, 1, 1, 1); - - frame = 0; - - vframes = 1; - hframes = 1; } Sprite2D::~Sprite2D() { diff --git a/scene/2d/sprite_2d.h b/scene/2d/sprite_2d.h index 599a9e937e..49df78c59d 100644 --- a/scene/2d/sprite_2d.h +++ b/scene/2d/sprite_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -35,30 +35,27 @@ #include "scene/resources/texture.h" class Sprite2D : public Node2D { - GDCLASS(Sprite2D, Node2D); Ref<Texture2D> texture; - Ref<Texture2D> normal_map; - Ref<Texture2D> specular; Color specular_color; - float shininess; + real_t shininess = 0.0; - bool centered; + bool centered = true; Point2 offset; - bool hflip; - bool vflip; - bool region; + bool hflip = false; + bool vflip = false; + bool region_enabled = false; Rect2 region_rect; - bool region_filter_clip; + bool region_filter_clip_enabled = false; - int frame; + int frame = 0; - int vframes; - int hframes; + int vframes = 1; + int hframes = 1; - void _get_rects(Rect2 &r_src_rect, Rect2 &r_dst_rect, bool &r_filter_clip) const; + void _get_rects(Rect2 &r_src_rect, Rect2 &r_dst_rect, bool &r_filter_clip_enabled) const; void _texture_changed(); @@ -67,20 +64,20 @@ protected: static void _bind_methods(); - virtual void _validate_property(PropertyInfo &property) const; + virtual void _validate_property(PropertyInfo &property) const override; public: #ifdef TOOLS_ENABLED - virtual Dictionary _edit_get_state() const; - virtual void _edit_set_state(const Dictionary &p_state); + virtual Dictionary _edit_get_state() const override; + virtual void _edit_set_state(const Dictionary &p_state) override; - virtual void _edit_set_pivot(const Point2 &p_pivot); - virtual Point2 _edit_get_pivot() const; - virtual bool _edit_use_pivot() const; - virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const; + virtual void _edit_set_pivot(const Point2 &p_pivot) override; + virtual Point2 _edit_get_pivot() const override; + virtual bool _edit_use_pivot() const override; + virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override; - virtual Rect2 _edit_get_rect() const; - virtual bool _edit_use_rect() const; + virtual Rect2 _edit_get_rect() const override; + virtual bool _edit_use_rect() const override; #endif bool is_pixel_opaque(const Point2 &p_point) const; @@ -88,18 +85,6 @@ public: void set_texture(const Ref<Texture2D> &p_texture); Ref<Texture2D> get_texture() const; - void set_normal_map(const Ref<Texture2D> &p_texture); - Ref<Texture2D> get_normal_map() const; - - void set_specular_map(const Ref<Texture2D> &p_texture); - Ref<Texture2D> get_specular_map() const; - - void set_specular_color(const Color &p_color); - Color get_specular_color() const; - - void set_shininess(float p_shininess); - float get_shininess() const; - void set_centered(bool p_center); bool is_centered() const; @@ -112,10 +97,10 @@ public: void set_flip_v(bool p_flip); bool is_flipped_v() const; - void set_region(bool p_region); - bool is_region() const; + void set_region_enabled(bool p_enabled); + bool is_region_enabled() const; - void set_region_filter_clip(bool p_enable); + void set_region_filter_clip_enabled(bool p_enabled); bool is_region_filter_clip_enabled() const; void set_region_rect(const Rect2 &p_region_rect); @@ -124,8 +109,8 @@ public: void set_frame(int p_frame); int get_frame() const; - void set_frame_coords(const Vector2 &p_coord); - Vector2 get_frame_coords() const; + void set_frame_coords(const Vector2i &p_coord); + Vector2i get_frame_coords() const; void set_vframes(int p_amount); int get_vframes() const; @@ -134,7 +119,7 @@ public: int get_hframes() const; Rect2 get_rect() const; - virtual Rect2 get_anchorable_rect() const; + virtual Rect2 get_anchorable_rect() const override; Sprite2D(); ~Sprite2D(); diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 1cf12e4421..a139a92ab4 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,1955 +30,3009 @@ #include "tile_map.h" -#include "collision_object_2d.h" #include "core/io/marshalls.h" -#include "core/method_bind_ext.gen.inc" -#include "core/os/os.h" -#include "scene/2d/area_2d.h" + #include "servers/navigation_server_2d.h" -#include "servers/physics_server_2d.h" -int TileMap::_get_quadrant_size() const { +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)); - if (y_sort_mode) - return 1; - else - return quadrant_size; + size = size.max(p_coords + Vector2i(1, 1)); + pattern[p_coords] = TileMapCell(p_source_id, p_atlas_coords, p_alternative_tile); } -void TileMap::_notification(int p_what) { +bool TileMapPattern::has_cell(const Vector2i &p_coords) const { + return pattern.has(p_coords); +} - switch (p_what) { +void TileMapPattern::remove_cell(const Vector2i &p_coords, bool p_update_size) { + ERR_FAIL_COND(!pattern.has(p_coords)); - case NOTIFICATION_ENTER_TREE: { + pattern.erase(p_coords); + if (p_update_size) { + size = Vector2i(); + for (Map<Vector2i, TileMapCell>::Element *E = pattern.front(); E; E = E->next()) { + size = size.max(E->key() + Vector2i(1, 1)); + } + } +} - Node2D *c = this; - while (c) { +int TileMapPattern::get_cell_source_id(const Vector2i &p_coords) const { + ERR_FAIL_COND_V(!pattern.has(p_coords), TileSet::INVALID_SOURCE); - navigation = Object::cast_to<Navigation2D>(c); - if (navigation) { - break; - } + return pattern[p_coords].source_id; +} - c = Object::cast_to<Node2D>(c->get_parent()); - } +Vector2i TileMapPattern::get_cell_atlas_coords(const Vector2i &p_coords) const { + ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetSource::INVALID_ATLAS_COORDS); - if (use_parent) { - _clear_quadrants(); - collision_parent = Object::cast_to<CollisionObject2D>(get_parent()); - } + return pattern[p_coords].get_atlas_coords(); +} - pending_update = true; - _recreate_quadrants(); - update_dirty_quadrants(); - RID space = get_world_2d()->get_space(); - _update_quadrant_transform(); - _update_quadrant_space(space); - update_configuration_warning(); +int TileMapPattern::get_cell_alternative_tile(const Vector2i &p_coords) const { + ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetSource::INVALID_TILE_ALTERNATIVE); - } break; + return pattern[p_coords].alternative_tile; +} - case NOTIFICATION_EXIT_TREE: { +TypedArray<Vector2i> TileMapPattern::get_used_cells() const { + // Returns the cells used in the tilemap. + TypedArray<Vector2i> a; + a.resize(pattern.size()); + int i = 0; + for (Map<Vector2i, TileMapCell>::Element *E = pattern.front(); E; E = E->next()) { + Vector2i p(E->key().x, E->key().y); + a[i++] = p; + } - _update_quadrant_space(RID()); - for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { + return a; +} - Quadrant &q = E->get(); - if (navigation) { - for (Map<PosKey, Quadrant::NavPoly>::Element *F = q.navpoly_ids.front(); F; F = F->next()) { +Vector2i TileMapPattern::get_size() const { + return size; +} - NavigationServer2D::get_singleton()->region_set_map(F->get().region, RID()); - } - q.navpoly_ids.clear(); - } +void TileMapPattern::set_size(const Vector2i &p_size) { + for (Map<Vector2i, TileMapCell>::Element *E = pattern.front(); E; E = E->next()) { + Vector2i coords = E->key(); + if (p_size.x <= coords.x || p_size.y <= coords.y) { + ERR_FAIL_MSG(vformat("Cannot set pattern size to %s, it contains a tile at %s. Size can only be increased.", p_size, coords)); + }; + } - if (collision_parent) { - collision_parent->remove_shape_owner(q.shape_owner_id); - q.shape_owner_id = -1; + size = p_size; +} + +bool TileMapPattern::is_empty() const { + return pattern.is_empty(); +}; + +void TileMapPattern::clear() { + size = Vector2i(); + pattern.clear(); +}; + +void TileMapPattern::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMapPattern::set_cell, DEFVAL(TileSet::INVALID_SOURCE), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetSource::INVALID_TILE_ALTERNATIVE)); + ClassDB::bind_method(D_METHOD("has_cell", "coords"), &TileMapPattern::has_cell); + ClassDB::bind_method(D_METHOD("remove_cell", "coords"), &TileMapPattern::remove_cell); + ClassDB::bind_method(D_METHOD("get_cell_source_id", "coords"), &TileMapPattern::get_cell_source_id); + ClassDB::bind_method(D_METHOD("get_cell_atlas_coords", "coords"), &TileMapPattern::get_cell_atlas_coords); + ClassDB::bind_method(D_METHOD("get_cell_alternative_tile", "coords"), &TileMapPattern::get_cell_alternative_tile); + + ClassDB::bind_method(D_METHOD("get_used_cells"), &TileMapPattern::get_used_cells); + ClassDB::bind_method(D_METHOD("get_size"), &TileMapPattern::get_size); + ClassDB::bind_method(D_METHOD("set_size", "size"), &TileMapPattern::set_size); + ClassDB::bind_method(D_METHOD("is_empty"), &TileMapPattern::is_empty); +} + +Vector2i TileMap::transform_coords_layout(Vector2i p_coords, TileSet::TileOffsetAxis p_offset_axis, TileSet::TileLayout p_from_layout, TileSet::TileLayout p_to_layout) { + // Transform to stacked layout. + Vector2i output = p_coords; + if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) { + SWAP(output.x, output.y); + } + switch (p_from_layout) { + case TileSet::TILE_LAYOUT_STACKED: + break; + case TileSet::TILE_LAYOUT_STACKED_OFFSET: + if (output.y % 2) { + output.x -= 1; + } + break; + case TileSet::TILE_LAYOUT_STAIRS_RIGHT: + case TileSet::TILE_LAYOUT_STAIRS_DOWN: + if ((p_from_layout == TileSet::TILE_LAYOUT_STAIRS_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { + if (output.y < 0 && bool(output.y % 2)) { + output = Vector2i(output.x + output.y / 2 - 1, output.y); + } else { + output = Vector2i(output.x + output.y / 2, output.y); } + } else { + if (output.x < 0 && bool(output.x % 2)) { + output = Vector2i(output.x / 2 - 1, output.x + output.y * 2); + } else { + output = Vector2i(output.x / 2, output.x + output.y * 2); + } + } + break; + case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: + case TileSet::TILE_LAYOUT_DIAMOND_DOWN: + if ((p_from_layout == TileSet::TILE_LAYOUT_DIAMOND_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { + if ((output.x + output.y) < 0 && (output.x - output.y) % 2) { + output = Vector2i((output.x + output.y) / 2 - 1, output.y - output.x); + } else { + output = Vector2i((output.x + output.y) / 2, -output.x + output.y); + } + } else { + if ((output.x - output.y) < 0 && (output.x + output.y) % 2) { + output = Vector2i((output.x - output.y) / 2 - 1, output.x + output.y); + } else { + output = Vector2i((output.x - output.y) / 2, output.x + output.y); + } + } + break; + } - for (Map<PosKey, Quadrant::Occluder>::Element *F = q.occluder_instances.front(); F; F = F->next()) { - RS::get_singleton()->free(F->get().id); + switch (p_to_layout) { + case TileSet::TILE_LAYOUT_STACKED: + break; + case TileSet::TILE_LAYOUT_STACKED_OFFSET: + if (output.y % 2) { + output.x += 1; + } + break; + case TileSet::TILE_LAYOUT_STAIRS_RIGHT: + case TileSet::TILE_LAYOUT_STAIRS_DOWN: + if ((p_to_layout == TileSet::TILE_LAYOUT_STAIRS_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { + if (output.y < 0 && (output.y % 2)) { + output = Vector2i(output.x - output.y / 2 + 1, output.y); + } else { + output = Vector2i(output.x - output.y / 2, output.y); + } + } else { + if (output.y % 2) { + if (output.y < 0) { + output = Vector2i(2 * output.x + 1, -output.x + output.y / 2 - 1); + } else { + output = Vector2i(2 * output.x + 1, -output.x + output.y / 2); + } + } else { + output = Vector2i(2 * output.x, -output.x + output.y / 2); } - q.occluder_instances.clear(); } + break; + case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: + case TileSet::TILE_LAYOUT_DIAMOND_DOWN: + if ((p_to_layout == TileSet::TILE_LAYOUT_DIAMOND_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { + if (output.y % 2) { + if (output.y > 0) { + output = Vector2i(output.x - output.y / 2, output.x + output.y / 2 + 1); + } else { + output = Vector2i(output.x - output.y / 2 + 1, output.x + output.y / 2); + } + } else { + output = Vector2i(output.x - output.y / 2, output.x + output.y / 2); + } + } else { + if (output.y % 2) { + if (output.y < 0) { + output = Vector2i(output.x + output.y / 2, -output.x + output.y / 2 - 1); + } else { + output = Vector2i(output.x + output.y / 2 + 1, -output.x + output.y / 2); + } + } else { + output = Vector2i(output.x + output.y / 2, -output.x + output.y / 2); + } + } + break; + } - collision_parent = nullptr; - navigation = nullptr; + if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) { + SWAP(output.x, output.y); + } - } break; + return output; +} - case NOTIFICATION_TRANSFORM_CHANGED: { +int TileMap::get_effective_quadrant_size(int p_layer) const { + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), 1); - //move stuff - _update_quadrant_transform(); + // When using YSort, the quadrant size is reduced to 1 to have one CanvasItem per quadrant + if (is_y_sort_enabled() && layers[p_layer].y_sort_enabled) { + return 1; + } else { + return quadrant_size; + } +} - } break; - case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { +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(); +} - if (use_parent) { - _recreate_quadrants(); - } +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; + _clear_internals(); + _recreate_internals(); } break; + case NOTIFICATION_EXIT_TREE: { + _clear_internals(); + } break; + } + + // Transfers the notification to tileset plugins. + if (tile_set.is_valid()) { + _rendering_notification(p_what); + _physics_notification(p_what); + _navigation_notification(p_what); } } -void TileMap::_update_quadrant_space(const RID &p_space) { +Ref<TileSet> TileMap::get_tileset() const { + return tile_set; +} - if (!use_parent) { - for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { +void TileMap::set_tileset(const Ref<TileSet> &p_tileset) { + if (p_tileset == tile_set) { + return; + } - Quadrant &q = E->get(); - PhysicsServer2D::get_singleton()->body_set_space(q.body, p_space); - } + // Set the tileset, registering to its changes. + if (tile_set.is_valid()) { + tile_set->disconnect("changed", callable_mp(this, &TileMap::_tile_set_changed)); + } + + if (!p_tileset.is_valid()) { + _clear_internals(); } + + tile_set = p_tileset; + + if (tile_set.is_valid()) { + tile_set->connect("changed", callable_mp(this, &TileMap::_tile_set_changed)); + _clear_internals(); + _recreate_internals(); + } + + emit_signal(SNAME("changed")); } -void TileMap::_update_quadrant_transform() { +void TileMap::set_quadrant_size(int p_size) { + ERR_FAIL_COND_MSG(p_size < 1, "TileMapQuadrant size cannot be smaller than 1."); - if (!is_inside_tree()) - return; + quadrant_size = p_size; + _clear_internals(); + _recreate_internals(); + emit_signal(SNAME("changed")); +} - Transform2D global_transform = get_global_transform(); +int TileMap::get_quadrant_size() const { + return quadrant_size; +} - Transform2D local_transform; - if (collision_parent) - local_transform = get_transform(); +int TileMap::get_layers_count() const { + return layers.size(); +} - Transform2D nav_rel; - if (navigation) - nav_rel = get_relative_transform_to_parent(navigation); +void TileMap::add_layer(int p_to_pos) { + if (p_to_pos < 0) { + p_to_pos = layers.size(); + } - for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { + ERR_FAIL_INDEX(p_to_pos, (int)layers.size() + 1); - Quadrant &q = E->get(); - Transform2D xform; - xform.set_origin(q.pos); + // Must clear before adding the layer. + _clear_internals(); - if (!use_parent) { - xform = global_transform * xform; - PhysicsServer2D::get_singleton()->body_set_state(q.body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); - } + layers.insert(p_to_pos, TileMapLayer()); + _recreate_internals(); + notify_property_list_changed(); - if (navigation) { - for (Map<PosKey, Quadrant::NavPoly>::Element *F = q.navpoly_ids.front(); F; F = F->next()) { + emit_signal(SNAME("changed")); - NavigationServer2D::get_singleton()->region_set_transform(F->get().region, nav_rel * F->get().xform); - } - } + update_configuration_warnings(); +} - for (Map<PosKey, Quadrant::Occluder>::Element *F = q.occluder_instances.front(); F; F = F->next()) { - RS::get_singleton()->canvas_light_occluder_set_transform(F->get().id, global_transform * F->get().xform); - } +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::set_tileset(const Ref<TileSet> &p_tileset) { +void TileMap::remove_layer(int p_layer) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); - if (tile_set.is_valid()) { - tile_set->disconnect("changed", callable_mp(this, &TileMap::_recreate_quadrants)); - tile_set->remove_change_receptor(this); - } + // Clear before removing the layer. + _clear_internals(); - _clear_quadrants(); - tile_set = p_tileset; + layers.remove(p_layer); + _recreate_internals(); + notify_property_list_changed(); - if (tile_set.is_valid()) { - tile_set->connect("changed", callable_mp(this, &TileMap::_recreate_quadrants)); - tile_set->add_change_receptor(this); - } else { - clear(); + if (selected_layer >= p_layer) { + selected_layer -= 1; } - _recreate_quadrants(); - emit_signal("settings_changed"); + emit_signal(SNAME("changed")); + + update_configuration_warnings(); } -Ref<TileSet> TileMap::get_tileset() const { +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")); +} - return tile_set; +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_cell_size(Size2 p_size) { +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")); - ERR_FAIL_COND(p_size.x < 1 || p_size.y < 1); + update_configuration_warnings(); +} - _clear_quadrants(); - cell_size = p_size; - _recreate_quadrants(); - emit_signal("settings_changed"); +bool TileMap::is_layer_enabled(int p_layer) const { + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), false); + return layers[p_layer].enabled; } -Size2 TileMap::get_cell_size() const { +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")); - return cell_size; + update_configuration_warnings(); } -void TileMap::set_quadrant_size(int p_size) { +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; +} - ERR_FAIL_COND_MSG(p_size < 1, "Quadrant size cannot be smaller than 1."); +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")); +} - _clear_quadrants(); - quadrant_size = p_size; - _recreate_quadrants(); - emit_signal("settings_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; } -int TileMap::get_quadrant_size() const { +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")); - return quadrant_size; + update_configuration_warnings(); } -void TileMap::_fix_cell_transform(Transform2D &xform, const Cell &p_cell, const Vector2 &p_offset, const Size2 &p_sc) { - - Size2 s = p_sc; - Vector2 offset = p_offset; +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; +} - if (compatibility_mode && !centered_textures) { +void TileMap::set_collision_visibility_mode(TileMap::VisibilityMode p_show_collision) { + collision_visibility_mode = p_show_collision; + _clear_internals(); + _recreate_internals(); + emit_signal(SNAME("changed")); +} - if (tile_origin == TILE_ORIGIN_BOTTOM_LEFT) { - offset.y += cell_size.y; - } else if (tile_origin == TILE_ORIGIN_CENTER) { - offset += cell_size / 2; - } +TileMap::VisibilityMode TileMap::get_collision_visibility_mode() { + return collision_visibility_mode; +} - if (s.y > s.x) { - if ((p_cell.flip_h && (p_cell.flip_v || p_cell.transpose)) || (p_cell.flip_v && !p_cell.transpose)) { - offset.y += s.y - s.x; - } - } else if (s.y < s.x) { - if ((p_cell.flip_v && (p_cell.flip_h || p_cell.transpose)) || (p_cell.flip_h && !p_cell.transpose)) { - offset.x += s.x - s.y; - } - } - } +void TileMap::set_navigation_visibility_mode(TileMap::VisibilityMode p_show_navigation) { + navigation_visibility_mode = p_show_navigation; + _clear_internals(); + _recreate_internals(); + emit_signal(SNAME("changed")); +} - if (p_cell.transpose) { - SWAP(xform.elements[0].x, xform.elements[0].y); - SWAP(xform.elements[1].x, xform.elements[1].y); - SWAP(offset.x, offset.y); - SWAP(s.x, s.y); - } +TileMap::VisibilityMode TileMap::get_navigation_visibility_mode() { + return navigation_visibility_mode; +} - if (p_cell.flip_h) { - xform.elements[0].x = -xform.elements[0].x; - xform.elements[1].x = -xform.elements[1].x; - if (compatibility_mode && !centered_textures) { - if (tile_origin == TILE_ORIGIN_TOP_LEFT || tile_origin == TILE_ORIGIN_BOTTOM_LEFT) { - offset.x = s.x - offset.x; - } else if (tile_origin == TILE_ORIGIN_CENTER) { - offset.x = s.x - offset.x / 2; - } - } else { - offset.x = s.x - offset.x; - } - } +void TileMap::set_y_sort_enabled(bool p_enable) { + Node2D::set_y_sort_enabled(p_enable); + _clear_internals(); + _recreate_internals(); + emit_signal(SNAME("changed")); +} - if (p_cell.flip_v) { - xform.elements[0].y = -xform.elements[0].y; - xform.elements[1].y = -xform.elements[1].y; - if (compatibility_mode && !centered_textures) { - if (tile_origin == TILE_ORIGIN_TOP_LEFT) { - offset.y = s.y - offset.y; - } else if (tile_origin == TILE_ORIGIN_BOTTOM_LEFT) { - offset.y += s.y; - } else if (tile_origin == TILE_ORIGIN_CENTER) { - offset.y += s.y; - } - } else { - offset.y = s.y - offset.y; - } - } +Vector2i TileMap::_coords_to_quadrant_coords(int p_layer, const Vector2i &p_coords) const { + int quadrant_size = get_effective_quadrant_size(p_layer); - if (centered_textures) { - offset += cell_size / 2 - s / 2; - } - xform.elements[2] += offset; + // 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); } -void TileMap::_add_shape(int &shape_idx, const Quadrant &p_q, const Ref<Shape2D> &p_shape, const TileSet::ShapeData &p_shape_data, const Transform2D &p_xform, const Vector2 &p_metadata) { - PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); +Map<Vector2i, TileMapQuadrant>::Element *TileMap::_create_quadrant(int p_layer, const Vector2i &p_qk) { + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), nullptr); - if (!use_parent) { - ps->body_add_shape(p_q.body, p_shape->get_rid(), p_xform); - ps->body_set_shape_metadata(p_q.body, shape_idx, p_metadata); - ps->body_set_shape_as_one_way_collision(p_q.body, shape_idx, p_shape_data.one_way_collision, p_shape_data.one_way_collision_margin); + TileMapQuadrant q; + q.layer = p_layer; + q.coords = p_qk; - } else if (collision_parent) { - Transform2D xform = p_xform; - xform.set_origin(xform.get_origin() + p_q.pos); + rect_cache_dirty = true; - collision_parent->shape_owner_add_shape(p_q.shape_owner_id, p_shape); + // 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()); - int real_index = collision_parent->shape_owner_get_shape_index(p_q.shape_owner_id, shape_idx); - RID rid = collision_parent->get_rid(); + // Call the create_quadrant method on plugins + if (tile_set.is_valid()) { + _rendering_create_quadrant(&q); + _physics_create_quadrant(&q); + } - if (Object::cast_to<Area2D>(collision_parent) != nullptr) { - ps->area_set_shape_transform(rid, real_index, get_transform() * xform); - } else { - ps->body_set_shape_transform(rid, real_index, get_transform() * xform); - ps->body_set_shape_metadata(rid, real_index, p_metadata); - ps->body_set_shape_as_one_way_collision(rid, real_index, p_shape_data.one_way_collision, p_shape_data.one_way_collision_margin); + 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); + } } } - shape_idx++; + _queue_update_dirty_quadrants(); } -void TileMap::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")); +} - if (!pending_update) +void TileMap::_update_dirty_quadrants() { + if (!pending_update) { return; + } if (!is_inside_tree() || !tile_set.is_valid()) { pending_update = false; return; } - RenderingServer *vs = RenderingServer::get_singleton(); - PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); - Vector2 tofs = get_cell_draw_offset(); - Transform2D nav_rel; - if (navigation) - nav_rel = get_relative_transform_to_parent(navigation); - - Vector2 qofs; - - SceneTree *st = SceneTree::get_singleton(); - Color debug_collision_color; - Color debug_navigation_color; + 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; + } + } - bool debug_shapes = st && st->is_debugging_collisions_hint(); - if (debug_shapes) { - debug_collision_color = st->get_debug_collisions_color(); - } + // 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()); + } - bool debug_navigation = st && st->is_debugging_navigation_hint(); - if (debug_navigation) { - debug_navigation_color = st->get_debug_navigation_color(); + // Clear the list + while (layers[layer].dirty_quadrant_list.first()) { + layers[layer].dirty_quadrant_list.remove(layers[layer].dirty_quadrant_list.first()); + } } - while (dirty_quadrant_list.first()) { + pending_update = false; - Quadrant &q = *dirty_quadrant_list.first()->self(); + _recompute_rect_cache(); +} - for (List<RID>::Element *E = q.canvas_items.front(); E; E = E->next()) { +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."); - vs->free(E->get()); + if (!layers[layer].enabled) { + continue; } - q.canvas_items.clear(); + // Upadate the layer internals. + _rendering_update_layer(layer); - if (!use_parent) { - ps->body_clear_shapes(q.body); - } else if (collision_parent) { - collision_parent->shape_owner_clear_shapes(q.shape_owner_id); - } - int shape_idx = 0; + // 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)); - if (navigation) { - for (Map<PosKey, Quadrant::NavPoly>::Element *E = q.navpoly_ids.front(); E; E = E->next()) { - - NavigationServer2D::get_singleton()->region_set_map(E->get().region, RID()); + 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); } - q.navpoly_ids.clear(); - } - for (Map<PosKey, Quadrant::Occluder>::Element *E = q.occluder_instances.front(); E; E = E->next()) { - RS::get_singleton()->free(E->get().id); + Vector2i pk = E->key(); + Q->get().cells.insert(pk); + + _make_quadrant_dirty(Q); } - q.occluder_instances.clear(); - Ref<ShaderMaterial> prev_material; - int prev_z_index = 0; - RID prev_canvas_item; - RID prev_debug_canvas_item; + } - for (int i = 0; i < q.cells.size(); i++) { + _update_dirty_quadrants(); +} - Map<PosKey, Cell>::Element *E = tile_map.find(q.cells[i]); - Cell &c = E->get(); - //moment of truth - if (!tile_set->has_tile(c.id)) - continue; - Ref<Texture2D> tex = tile_set->tile_get_texture(c.id); - Vector2 tile_ofs = tile_set->tile_get_texture_offset(c.id); +void TileMap::_erase_quadrant(Map<Vector2i, TileMapQuadrant>::Element *Q) { + // Remove a quadrant. + TileMapQuadrant *q = &(Q->get()); - Vector2 wofs = _map_to_world(E->key().x, E->key().y); - Vector2 offset = wofs - q.pos + tofs; + // 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); + } - if (!tex.is_valid()) - continue; + // 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)); + } - Ref<ShaderMaterial> mat = tile_set->tile_get_material(c.id); - int z_index = tile_set->tile_get_z_index(c.id); + // Free the debug canvas item. + RenderingServer *rs = RenderingServer::get_singleton(); + rs->free(q->debug_canvas_item); - if (tile_set->tile_get_tile_mode(c.id) == TileSet::AUTO_TILE || - tile_set->tile_get_tile_mode(c.id) == TileSet::ATLAS_TILE) { - z_index += tile_set->autotile_get_z_index(c.id, Vector2(c.autotile_coord_x, c.autotile_coord_y)); - } + layers[q->layer].quadrant_map.erase(Q); + rect_cache_dirty = true; +} - RID canvas_item; - RID debug_canvas_item; +void TileMap::_clear_layer_internals(int p_layer) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); - if (prev_canvas_item == RID() || prev_material != mat || prev_z_index != z_index) { + // Clear quadrants. + while (layers[p_layer].quadrant_map.size()) { + _erase_quadrant(layers[p_layer].quadrant_map.front()); + } - canvas_item = vs->canvas_item_create(); - if (mat.is_valid()) - vs->canvas_item_set_material(canvas_item, mat->get_rid()); - vs->canvas_item_set_parent(canvas_item, get_canvas_item()); - _update_item_material_state(canvas_item); - Transform2D xform; - xform.set_origin(q.pos); - vs->canvas_item_set_transform(canvas_item, xform); - vs->canvas_item_set_light_mask(canvas_item, get_light_mask()); - vs->canvas_item_set_z_index(canvas_item, z_index); + // Clear the layers internals. + _rendering_cleanup_layer(p_layer); - q.canvas_items.push_back(canvas_item); + // 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()); + } +} - if (debug_shapes) { +void TileMap::_clear_internals() { + // Clear quadrants. + for (unsigned int layer = 0; layer < layers.size(); layer++) { + _clear_layer_internals(layer); + } +} - debug_canvas_item = vs->canvas_item_create(); - vs->canvas_item_set_parent(debug_canvas_item, canvas_item); - vs->canvas_item_set_z_as_relative_to_parent(debug_canvas_item, false); - vs->canvas_item_set_z_index(debug_canvas_item, RS::CANVAS_ITEM_Z_MAX - 1); - q.canvas_items.push_back(debug_canvas_item); - prev_debug_canvas_item = debug_canvas_item; - } +void TileMap::_recompute_rect_cache() { + // Compute the displayed area of the tilemap. +#ifdef DEBUG_ENABLED - prev_canvas_item = canvas_item; - prev_material = mat; - prev_z_index = z_index; + if (!rect_cache_dirty) { + return; + } + Rect2 r_total; + 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 { - canvas_item = prev_canvas_item; - if (debug_shapes) { - debug_canvas_item = prev_debug_canvas_item; - } + r_total = r_total.merge(r); } + } + } - Rect2 r = tile_set->tile_get_region(c.id); - if (tile_set->tile_get_tile_mode(c.id) == TileSet::AUTO_TILE || tile_set->tile_get_tile_mode(c.id) == TileSet::ATLAS_TILE) { - int spacing = tile_set->autotile_get_spacing(c.id); - r.size = tile_set->autotile_get_size(c.id); - r.position += (r.size + Vector2(spacing, spacing)) * Vector2(c.autotile_coord_x, c.autotile_coord_y); - } + rect_cache = r_total; + + item_rect_changed(); + + rect_cache_dirty = false; +#endif +} - Size2 s; - if (r == Rect2()) - s = tex->get_size(); - else - s = r.size; - - Rect2 rect; - rect.position = offset.floor(); - rect.size = s; - rect.size.x += fp_adjust; - rect.size.y += fp_adjust; - - if (compatibility_mode && !centered_textures) { - if (rect.size.y > rect.size.x) { - if ((c.flip_h && (c.flip_v || c.transpose)) || (c.flip_v && !c.transpose)) - tile_ofs.y += rect.size.y - rect.size.x; - } else if (rect.size.y < rect.size.x) { - if ((c.flip_v && (c.flip_h || c.transpose)) || (c.flip_h && !c.transpose)) - tile_ofs.x += rect.size.x - rect.size.y; +/////////////////////////////// Rendering ////////////////////////////////////// + +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(); - if (c.transpose) { - SWAP(tile_ofs.x, tile_ofs.y); - if (centered_textures) { - rect.position.x += cell_size.x / 2 - rect.size.y / 2; - rect.position.y += cell_size.y / 2 - rect.size.x / 2; + // 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); + } + } } - } else if (centered_textures) { - rect.position += cell_size / 2 - rect.size / 2; } - - if (c.flip_h) { - rect.size.x = -rect.size.x; - tile_ofs.x = -tile_ofs.x; + } 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; + } +} - if (c.flip_v) { - rect.size.y = -rect.size.y; - tile_ofs.y = -tile_ofs.y; - } +void TileMap::_rendering_update_layer(int p_layer) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); - if (compatibility_mode && !centered_textures) { - if (tile_origin == TILE_ORIGIN_TOP_LEFT) { - rect.position += tile_ofs; + RenderingServer *rs = RenderingServer::get_singleton(); + if (!layers[p_layer].canvas_item.is_valid()) { + RID ci = rs->canvas_item_create(); + rs->canvas_item_set_parent(ci, get_canvas_item()); - } else if (tile_origin == TILE_ORIGIN_BOTTOM_LEFT) { + /*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); - rect.position += tile_ofs; + 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()); +} - if (c.transpose) { - if (c.flip_h) - rect.position.x -= cell_size.x; - else - rect.position.x += cell_size.x; - } else { - if (c.flip_v) - rect.position.y -= cell_size.y; - else - rect.position.y += cell_size.y; - } +void TileMap::_rendering_cleanup_layer(int p_layer) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); - } else if (tile_origin == TILE_ORIGIN_CENTER) { + RenderingServer *rs = RenderingServer::get_singleton(); + if (!layers[p_layer].canvas_item.is_valid()) { + rs->free(layers[p_layer].canvas_item); + } +} - rect.position += tile_ofs; +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()); - if (c.flip_h) - rect.position.x -= cell_size.x / 2; - else - rect.position.x += cell_size.x / 2; + bool visible = is_visible_in_tree(); - if (c.flip_v) - rect.position.y -= cell_size.y / 2; - else - rect.position.y += cell_size.y / 2; - } - } else { - rect.position += tile_ofs; - } + SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); + while (q_list_element) { + TileMapQuadrant &q = *q_list_element->self(); - Ref<Texture2D> normal_map = tile_set->tile_get_normal_map(c.id); - Color modulate = tile_set->tile_get_modulate(c.id); - Color self_modulate = get_self_modulate(); - modulate = Color(modulate.r * self_modulate.r, modulate.g * self_modulate.g, - modulate.b * self_modulate.b, modulate.a * self_modulate.a); - if (r == Rect2()) { - tex->draw_rect(canvas_item, rect, false, modulate, c.transpose, normal_map); - } else { - tex->draw_rect_region(canvas_item, rect, r, modulate, c.transpose, normal_map, Ref<Texture2D>(), Color(1, 1, 1, 1), RS::CANVAS_ITEM_TEXTURE_FILTER_DEFAULT, RS::CANVAS_ITEM_TEXTURE_REPEAT_DEFAULT, clip_uv); - } + RenderingServer *rs = RenderingServer::get_singleton(); - Vector<TileSet::ShapeData> shapes = tile_set->tile_get_shapes(c.id); + // Free the canvas items. + for (const RID &ci : q.canvas_items) { + rs->free(ci); + } + q.canvas_items.clear(); - for (int j = 0; j < shapes.size(); j++) { - Ref<Shape2D> shape = shapes[j].shape; - if (shape.is_valid()) { - if (tile_set->tile_get_tile_mode(c.id) == TileSet::SINGLE_TILE || (shapes[j].autotile_coord.x == c.autotile_coord_x && shapes[j].autotile_coord.y == c.autotile_coord_y)) { - Transform2D xform; - xform.set_origin(offset.floor()); + // Free the occluders. + for (const RID &occluder : q.occluders) { + rs->free(occluder); + } + q.occluders.clear(); - Vector2 shape_ofs = shapes[j].shape_transform.get_origin(); + // Those allow to group cell per material or z-index. + Ref<ShaderMaterial> prev_material; + int prev_z_index = 0; + RID prev_canvas_item; - _fix_cell_transform(xform, c, shape_ofs, s); + // 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); - xform *= shapes[j].shape_transform.untranslated(); + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); - if (debug_canvas_item.is_valid()) { - vs->canvas_item_add_set_transform(debug_canvas_item, xform); - shape->draw(debug_canvas_item, debug_collision_color); - } + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } - if (shape->has_meta("decomposed")) { - Array _shapes = shape->get_meta("decomposed"); - for (int k = 0; k < _shapes.size(); k++) { - Ref<ConvexPolygonShape2D> convex = _shapes[k]; - if (convex.is_valid()) { - _add_shape(shape_idx, q, convex, shapes[j], xform, Vector2(E->key().x, E->key().y)); -#ifdef DEBUG_ENABLED - } else { - print_error("The TileSet assigned to the TileMap " + get_name() + " has an invalid convex shape."); -#endif - } - } - } else { - _add_shape(shape_idx, q, shape, shapes[j], xform, Vector2(E->key().x, E->key().y)); - } + 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(); } - } - } - if (debug_canvas_item.is_valid()) { - vs->canvas_item_add_set_transform(debug_canvas_item, Transform2D()); - } + // --- CanvasItems --- + // Create two canvas items, for rendering and debug. + RID canvas_item; - if (navigation) { - Ref<NavigationPolygon> navpoly; - Vector2 npoly_ofs; - if (tile_set->tile_get_tile_mode(c.id) == TileSet::AUTO_TILE || tile_set->tile_get_tile_mode(c.id) == TileSet::ATLAS_TILE) { - navpoly = tile_set->autotile_get_navigation_polygon(c.id, Vector2(c.autotile_coord_x, c.autotile_coord_y)); - npoly_ofs = Vector2(); - } else { - navpoly = tile_set->tile_get_navigation_polygon(c.id); - npoly_ofs = tile_set->tile_get_navigation_polygon_offset(c.id); - } + // 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()); - if (navpoly.is_valid()) { - Transform2D xform; - xform.set_origin(offset.floor() + q.pos); - _fix_cell_transform(xform, c, npoly_ofs, s); - - RID region = NavigationServer2D::get_singleton()->region_create(); - NavigationServer2D::get_singleton()->region_set_map(region, navigation->get_rid()); - NavigationServer2D::get_singleton()->region_set_transform(region, nav_rel * xform); - NavigationServer2D::get_singleton()->region_set_navpoly(region, navpoly); - - Quadrant::NavPoly np; - np.region = region; - np.xform = xform; - q.navpoly_ids[E->key()] = np; - - if (debug_navigation) { - RID debug_navigation_item = vs->canvas_item_create(); - vs->canvas_item_set_parent(debug_navigation_item, canvas_item); - vs->canvas_item_set_z_as_relative_to_parent(debug_navigation_item, false); - vs->canvas_item_set_z_index(debug_navigation_item, RS::CANVAS_ITEM_Z_MAX - 2); // Display one below collision debug - - if (debug_navigation_item.is_valid()) { - Vector<Vector2> navigation_polygon_vertices = navpoly->get_vertices(); - int vsize = navigation_polygon_vertices.size(); - - if (vsize > 2) { - Vector<Color> colors; - Vector<Vector2> vertices; - vertices.resize(vsize); - colors.resize(vsize); - { - const Vector2 *vr = navigation_polygon_vertices.ptr(); - for (int j = 0; j < vsize; j++) { - vertices.write[j] = vr[j]; - colors.write[j] = debug_navigation_color; - } - } + Transform2D xform; + xform.set_origin(position); + rs->canvas_item_set_transform(canvas_item, xform); - Vector<int> indices; + rs->canvas_item_set_light_mask(canvas_item, get_light_mask()); + rs->canvas_item_set_z_index(canvas_item, z_index); - for (int j = 0; j < navpoly->get_polygon_count(); j++) { - Vector<int> polygon = navpoly->get_polygon(j); + 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())); - for (int k = 2; k < polygon.size(); k++) { + q.canvas_items.push_back(canvas_item); - int kofs[3] = { 0, k - 1, k }; - for (int l = 0; l < 3; l++) { + prev_canvas_item = canvas_item; + prev_material = mat; + prev_z_index = z_index; - int idx = polygon[kofs[l]]; - ERR_FAIL_INDEX(idx, vsize); - indices.push_back(idx); - } - } - } - Transform2D navxform; - navxform.set_origin(offset.floor()); - _fix_cell_transform(navxform, c, npoly_ofs, s); + } else { + // Keep the same canvas_item to draw on. + canvas_item = prev_canvas_item; + } - vs->canvas_item_set_transform(debug_navigation_item, navxform); - vs->canvas_item_add_triangle_array(debug_navigation_item, indices, vertices, colors); - } + // 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); - Ref<OccluderPolygon2D> occluder; - if (tile_set->tile_get_tile_mode(c.id) == TileSet::AUTO_TILE || tile_set->tile_get_tile_mode(c.id) == TileSet::ATLAS_TILE) { - occluder = tile_set->autotile_get_light_occluder(c.id, Vector2(c.autotile_coord_x, c.autotile_coord_y)); - } else { - occluder = tile_set->tile_get_light_occluder(c.id); - } - if (occluder.is_valid()) { - Vector2 occluder_ofs = tile_set->tile_get_occluder_offset(c.id); - Transform2D xform; - xform.set_origin(offset.floor() + q.pos); - _fix_cell_transform(xform, c, occluder_ofs, s); - - RID orid = RS::get_singleton()->canvas_light_occluder_create(); - RS::get_singleton()->canvas_light_occluder_set_transform(orid, get_global_transform() * xform); - RS::get_singleton()->canvas_light_occluder_set_polygon(orid, occluder->get_rid()); - RS::get_singleton()->canvas_light_occluder_attach_to_canvas(orid, get_canvas()); - RS::get_singleton()->canvas_light_occluder_set_light_mask(orid, occluder_light_mask); - Quadrant::Occluder oc; - oc.xform = xform; - oc.id = orid; - q.occluder_instances[E->key()] = oc; + // --- 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); + } + } + } } } - dirty_quadrant_list.remove(dirty_quadrant_list.first()); - quadrant_order_dirty = true; + _rendering_quadrant_order_dirty = true; + q_list_element = q_list_element->next(); } - pending_update = false; + // Reset the drawing indices + if (_rendering_quadrant_order_dirty) { + int index = -(int64_t)0x80000000; //always must be drawn below children. - if (quadrant_order_dirty) { - - int index = -(int64_t)0x80000000; //always must be drawn below children - for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { - - Quadrant &q = E->get(); - for (List<RID>::Element *F = q.canvas_items.front(); F; F = F->next()) { + 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(); + } - RS::get_singleton()->canvas_item_set_draw_index(F->get(), index++); + // 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++); + } } } - - quadrant_order_dirty = false; + _rendering_quadrant_order_dirty = false; } - - _recompute_rect_cache(); } -void TileMap::_recompute_rect_cache() { +void TileMap::_rendering_create_quadrant(TileMapQuadrant *p_quadrant) { + ERR_FAIL_COND(!tile_set.is_valid()); -#ifdef DEBUG_ENABLED + _rendering_quadrant_order_dirty = true; +} - if (!rect_cache_dirty) - return; +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(); - Rect2 r_total; - for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { + // 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()); - Rect2 r; - r.position = _map_to_world(E->key().x * _get_quadrant_size(), E->key().y * _get_quadrant_size()); - r.expand_to(_map_to_world(E->key().x * _get_quadrant_size() + _get_quadrant_size(), E->key().y * _get_quadrant_size())); - r.expand_to(_map_to_world(E->key().x * _get_quadrant_size() + _get_quadrant_size(), E->key().y * _get_quadrant_size() + _get_quadrant_size())); - r.expand_to(_map_to_world(E->key().x * _get_quadrant_size(), E->key().y * _get_quadrant_size() + _get_quadrant_size())); - if (E == quadrant_map.front()) - r_total = r; - else - r_total = r_total.merge(r); + if (!Engine::get_singleton()->is_editor_hint()) { + return; } - rect_cache = r_total; + // 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); - item_rect_changed(); + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); - rect_cache_dirty = false; -#endif + 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); + } + } + } + } } -Map<TileMap::PosKey, TileMap::Quadrant>::Element *TileMap::_create_quadrant(const PosKey &p_qk) { +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)); - Transform2D xform; - //xform.set_origin(Point2(p_qk.x,p_qk.y)*cell_size*quadrant_size); - Quadrant q; - q.pos = _map_to_world(p_qk.x * _get_quadrant_size(), p_qk.y * _get_quadrant_size()); - q.pos += get_cell_draw_offset(); - if (tile_origin == TILE_ORIGIN_CENTER) - q.pos += cell_size / 2; - else if (tile_origin == TILE_ORIGIN_BOTTOM_LEFT) - q.pos.y += cell_size.y; + 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; + } - xform.set_origin(q.pos); - //q.canvas_item = RenderingServer::get_singleton()->canvas_item_create(); - if (!use_parent) { - q.body = PhysicsServer2D::get_singleton()->body_create(); - PhysicsServer2D::get_singleton()->body_set_mode(q.body, use_kinematic ? PhysicsServer2D::BODY_MODE_KINEMATIC : PhysicsServer2D::BODY_MODE_STATIC); + // 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; + } - PhysicsServer2D::get_singleton()->body_attach_object_instance_id(q.body, get_instance_id()); - PhysicsServer2D::get_singleton()->body_set_collision_layer(q.body, collision_layer); - PhysicsServer2D::get_singleton()->body_set_collision_mask(q.body, collision_mask); - PhysicsServer2D::get_singleton()->body_set_param(q.body, PhysicsServer2D::BODY_PARAM_FRICTION, friction); - PhysicsServer2D::get_singleton()->body_set_param(q.body, PhysicsServer2D::BODY_PARAM_BOUNCE, bounce); + // Get tile data. + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile)); - if (is_inside_tree()) { - xform = get_global_transform() * xform; - RID space = get_world_2d()->get_space(); - PhysicsServer2D::get_singleton()->body_set_space(q.body, space); + // 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); } - PhysicsServer2D::get_singleton()->body_set_state(q.body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); - } else if (collision_parent) { - xform = get_transform() * xform; - q.shape_owner_id = collision_parent->create_shape_owner(this); - } else { - q.shape_owner_id = -1; - } + if (tile_data->get_flip_h()) { + dest_rect.size.x = -dest_rect.size.x; + } - rect_cache_dirty = true; - quadrant_order_dirty = true; - return quadrant_map.insert(p_qk, q); -} + if (tile_data->get_flip_v()) { + dest_rect.size.y = -dest_rect.size.y; + } -void TileMap::_erase_quadrant(Map<PosKey, Quadrant>::Element *Q) { + // 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); - Quadrant &q = Q->get(); - if (!use_parent) { - PhysicsServer2D::get_singleton()->free(q.body); - } else if (collision_parent) { - collision_parent->remove_shape_owner(q.shape_owner_id); + // Draw the tile. + tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); } +} - for (List<RID>::Element *E = q.canvas_items.front(); E; E = E->next()) { +/////////////////////////////// Physics ////////////////////////////////////// - RenderingServer::get_singleton()->free(E->get()); - } - q.canvas_items.clear(); - if (q.dirty_list.in_list()) - dirty_quadrant_list.remove(&q.dirty_list); +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(); - if (navigation) { - for (Map<PosKey, Quadrant::NavPoly>::Element *E = q.navpoly_ids.front(); E; E = E->next()) { + for (Map<Vector2i, TileMapQuadrant>::Element *E = layers[layer].quadrant_map.front(); E; E = E->next()) { + TileMapQuadrant &q = E->get(); - NavigationServer2D::get_singleton()->region_set_map(E->get().region, RID()); - } - q.navpoly_ids.clear(); - } + Transform2D xform; + xform.set_origin(map_to_world(E->key() * get_effective_quadrant_size(layer))); + xform = global_transform * xform; - for (Map<PosKey, Quadrant::Occluder>::Element *E = q.occluder_instances.front(); E; E = E->next()) { - RS::get_singleton()->free(E->get().id); + 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; } - q.occluder_instances.clear(); - - quadrant_map.erase(Q); - rect_cache_dirty = true; } -void TileMap::_make_quadrant_dirty(Map<PosKey, Quadrant>::Element *Q, bool update) { +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()); - Quadrant &q = Q->get(); - if (!q.dirty_list.in_list()) - dirty_quadrant_list.add(&q.dirty_list); + Transform2D global_transform = get_global_transform(); + PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); - if (pending_update) - return; - pending_update = true; - if (!is_inside_tree()) - return; + SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); + while (q_list_element) { + TileMapQuadrant &q = *q_list_element->self(); - if (update) { - call_deferred("update_dirty_quadrants"); - } -} + Vector2 quadrant_pos = map_to_world(q.coords * get_effective_quadrant_size(q.layer)); -void TileMap::set_cellv(const Vector2 &p_pos, int p_tile, bool p_flip_x, bool p_flip_y, bool p_transpose) { + LocalVector<int> body_shape_count; + body_shape_count.resize(q.bodies.size()); - set_cell(p_pos.x, p_pos.y, p_tile, p_flip_x, p_flip_y, p_transpose); -} + // 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; -void TileMap::_set_celld(const Vector2 &p_pos, const Dictionary &p_data) { + // 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); + } - Variant v_pos_x = p_pos.x, v_pos_y = p_pos.y, v_tile = p_data["id"], v_flip_h = p_data["flip_h"], v_flip_v = p_data["flip_y"], v_transpose = p_data["transpose"], v_autotile_coord = p_data["auto_coord"]; - const Variant *args[7] = { &v_pos_x, &v_pos_y, &v_tile, &v_flip_h, &v_flip_v, &v_transpose, &v_autotile_coord }; - Callable::CallError ce; - call("set_cell", args, 7, ce); -} + 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); -void TileMap::set_cell(int p_x, int p_y, int p_tile, bool p_flip_x, bool p_flip_y, bool p_transpose, Vector2 p_autotile_coord) { + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); - PosKey pk(p_x, p_y); + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } - Map<PosKey, Cell>::Element *E = tile_map.find(pk); - if (!E && p_tile == INVALID_CELL) - return; //nothing to do + 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)); - PosKey qk = pk.to_quadrant(_get_quadrant_size()); - if (p_tile == INVALID_CELL) { - //erase existing - tile_map.erase(pk); - Map<PosKey, Quadrant>::Element *Q = quadrant_map.find(qk); - ERR_FAIL_COND(!Q); - Quadrant &q = Q->get(); - q.cells.erase(pk); - if (q.cells.size() == 0) - _erase_quadrant(Q); - else - _make_quadrant_dirty(Q); + for (int body_index = 0; body_index < q.bodies.size(); body_index++) { + int &body_shape_index = body_shape_count[body_index]; - used_size_cache_dirty = true; - return; - } + // 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); - Map<PosKey, Quadrant>::Element *Q = quadrant_map.find(qk); + 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); - if (!E) { - E = tile_map.insert(pk, Cell()); - if (!Q) { - Q = _create_quadrant(qk); + // 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; + } + } + } + } + } } - Quadrant &q = Q->get(); - q.cells.insert(pk); - } else { - ERR_FAIL_COND(!Q); // quadrant should exist... - if (E->get().id == p_tile && E->get().flip_h == p_flip_x && E->get().flip_v == p_flip_y && E->get().transpose == p_transpose && E->get().autotile_coord_x == (uint16_t)p_autotile_coord.x && E->get().autotile_coord_y == (uint16_t)p_autotile_coord.y) - return; //nothing changed + q_list_element = q_list_element->next(); } +} - Cell &c = E->get(); +void TileMap::_physics_create_quadrant(TileMapQuadrant *p_quadrant) { + ERR_FAIL_COND(!tile_set.is_valid()); - c.id = p_tile; - c.flip_h = p_flip_x; - c.flip_v = p_flip_y; - c.transpose = p_transpose; - c.autotile_coord_x = (uint16_t)p_autotile_coord.x; - c.autotile_coord_y = (uint16_t)p_autotile_coord.y; + //Get the TileMap's gobla transform. + Transform2D global_transform; + if (is_inside_tree()) { + global_transform = get_global_transform(); + } - _make_quadrant_dirty(Q); - used_size_cache_dirty = true; -} + // Clear all bodies. + p_quadrant->bodies.clear(); -int TileMap::get_cellv(const Vector2 &p_pos) const { + // 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); - return get_cell(p_pos.x, p_pos.y); -} + 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)); -void TileMap::make_bitmask_area_dirty(const Vector2 &p_pos) { + 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()); + } - for (int x = p_pos.x - 1; x <= p_pos.x + 1; x++) { - for (int y = p_pos.y - 1; y <= p_pos.y + 1; y++) { - PosKey p(x, y); - if (dirty_bitmask.find(p) == nullptr) { - dirty_bitmask.push_back(p); - } + 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::update_bitmask_area(const Vector2 &p_pos) { - - for (int x = p_pos.x - 1; x <= p_pos.x + 1; x++) { - for (int y = p_pos.y - 1; y <= p_pos.y + 1; y++) { - update_cell_bitmask(x, y); - } +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::update_bitmask_region(const Vector2 &p_start, const Vector2 &p_end) { +void TileMap::_physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { + // Draw the debug collision shapes. + ERR_FAIL_COND(!tile_set.is_valid()); - if ((p_end.x < p_start.x || p_end.y < p_start.y) || (p_end.x == p_start.x && p_end.y == p_start.y)) { - int i; - Array a = get_used_cells(); - for (i = 0; i < a.size(); i++) { - // update_bitmask_area() in order to update cells adjacent to the - // current cell, since ordering in array may not be reliable - Vector2 vector = (Vector2)a[i]; - update_bitmask_area(Vector2(vector.x, vector.y)); - } + if (!get_tree()) { return; } - for (int x = p_start.x - 1; x <= p_end.x + 1; x++) { - for (int y = p_start.y - 1; y <= p_end.y + 1; y++) { - update_cell_bitmask(x, y); - } + + 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; } -} -void TileMap::update_cell_bitmask(int p_x, int p_y) { + RenderingServer *rs = RenderingServer::get_singleton(); - ERR_FAIL_COND_MSG(tile_set.is_null(), "Cannot update cell bitmask if Tileset is not open."); - PosKey p(p_x, p_y); - Map<PosKey, Cell>::Element *E = tile_map.find(p); - if (E != nullptr) { - int id = get_cell(p_x, p_y); - if (tile_set->tile_get_tile_mode(id) == TileSet::AUTO_TILE) { - uint16_t mask = 0; - if (tile_set->autotile_get_bitmask_mode(id) == TileSet::BITMASK_2X2) { - if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y))) { - mask |= TileSet::BIND_TOPLEFT; - } - if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y))) { - mask |= TileSet::BIND_TOPRIGHT; - } - if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y))) { - mask |= TileSet::BIND_BOTTOMLEFT; - } - if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y))) { - mask |= TileSet::BIND_BOTTOMRIGHT; - } - } else { - if (tile_set->autotile_get_bitmask_mode(id) == TileSet::BITMASK_3X3_MINIMAL) { - if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y))) { - mask |= TileSet::BIND_TOPLEFT; - } - if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y))) { - mask |= TileSet::BIND_TOPRIGHT; - } - if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y))) { - mask |= TileSet::BIND_BOTTOMLEFT; - } - if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y))) { - mask |= TileSet::BIND_BOTTOMRIGHT; - } - } else { - if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y - 1))) { - mask |= TileSet::BIND_TOPLEFT; - } - if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y - 1))) { - mask |= TileSet::BIND_TOPRIGHT; - } - if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y + 1))) { - mask |= TileSet::BIND_BOTTOMLEFT; - } - if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y + 1))) { - mask |= TileSet::BIND_BOTTOMRIGHT; - } - } - if (tile_set->is_tile_bound(id, get_cell(p_x, p_y - 1))) { - mask |= TileSet::BIND_TOP; - } - if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y))) { - mask |= TileSet::BIND_LEFT; - } - mask |= TileSet::BIND_CENTER; - if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y))) { - mask |= TileSet::BIND_RIGHT; - } - if (tile_set->is_tile_bound(id, get_cell(p_x, p_y + 1))) { - mask |= TileSet::BIND_BOTTOM; - } - } - Vector2 coord = tile_set->autotile_get_subtile_for_bitmask(id, mask, this, Vector2(p_x, p_y)); - E->get().autotile_coord_x = (int)coord.x; - E->get().autotile_coord_y = (int)coord.y; + Vector2 quadrant_pos = map_to_world(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer)); - PosKey qk = p.to_quadrant(_get_quadrant_size()); - Map<PosKey, Quadrant>::Element *Q = quadrant_map.find(qk); - _make_quadrant_dirty(Q); + 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); - } else if (tile_set->tile_get_tile_mode(id) == TileSet::SINGLE_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); - E->get().autotile_coord_x = 0; - E->get().autotile_coord_y = 0; - } else if (tile_set->tile_get_tile_mode(id) == TileSet::ATLAS_TILE) { + if (tile_set->has_source(c.source_id)) { + TileSetSource *source = *tile_set->get_source(c.source_id); - if (tile_set->autotile_get_bitmask(id, Vector2(p_x, p_y)) == TileSet::BIND_CENTER) { - Vector2 coord = tile_set->atlastile_get_subtile_by_priority(id, this, Vector2(p_x, p_y)); + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } - E->get().autotile_coord_x = (int)coord.x; - E->get().autotile_coord_y = (int)coord.y; + 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()); } -} +}; -void TileMap::update_dirty_bitmask() { +/////////////////////////////// Navigation ////////////////////////////////////// - while (dirty_bitmask.size() > 0) { - update_cell_bitmask(dirty_bitmask[0].x, dirty_bitmask[0].y); - dirty_bitmask.pop_front(); +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::fix_invalid_tiles() { - - ERR_FAIL_COND_MSG(tile_set.is_null(), "Cannot fix invalid tiles if Tileset is not open."); - for (Map<PosKey, Cell>::Element *E = tile_map.front(); E; E = E->next()) { +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()); - if (!tile_set->has_tile(get_cell(E->key().x, E->key().y))) { - set_cell(E->key().x, E->key().y, INVALID_CELL); - } + // 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(); } -} -int TileMap::get_cell(int p_x, int p_y) const { + 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(); - PosKey pk(p_x, p_y); + // 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); - const Map<PosKey, Cell>::Element *E = tile_map.find(pk); + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); - if (!E) - return INVALID_CELL; + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } - return E->get().id; -} -bool TileMap::is_cell_x_flipped(int p_x, int p_y) const { + 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()); - PosKey pk(p_x, p_y); + 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); - const Map<PosKey, Cell>::Element *E = tile_map.find(pk); + if (navpoly.is_valid()) { + Transform2D tile_transform; + tile_transform.set_origin(map_to_world(E_cell->get())); - if (!E) - return false; + 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; + } + } + } + } + } - return E->get().flip_h; + q_list_element = q_list_element->next(); + } } -bool TileMap::is_cell_y_flipped(int p_x, int p_y) const { - - PosKey pk(p_x, p_y); - - const Map<PosKey, Cell>::Element *E = tile_map.find(pk); - - if (!E) - return false; - return E->get().flip_v; +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(); } -bool TileMap::is_cell_transposed(int p_x, int p_y) const { - PosKey pk(p_x, p_y); +void TileMap::_navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { + // Draw the debug collision shapes. + ERR_FAIL_COND(!tile_set.is_valid()); - const Map<PosKey, Cell>::Element *E = tile_map.find(pk); + if (!get_tree()) { + return; + } - if (!E) - return false; + 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; + } - return E->get().transpose; -} + RenderingServer *rs = RenderingServer::get_singleton(); -void TileMap::set_cell_autotile_coord(int p_x, int p_y, const Vector2 &p_coord) { + Color color = get_tree()->get_debug_navigation_color(); + RandomPCG rand; - PosKey pk(p_x, p_y); + Vector2 quadrant_pos = map_to_world(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer)); - const Map<PosKey, Cell>::Element *E = tile_map.find(pk); + 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); - if (!E) - return; + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); - Cell c = E->get(); - c.autotile_coord_x = p_coord.x; - c.autotile_coord_y = p_coord.y; - tile_map[pk] = c; + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } - PosKey qk = pk.to_quadrant(_get_quadrant_size()); - Map<PosKey, Quadrant>::Element *Q = quadrant_map.find(qk); + 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)); - if (!Q) - return; + 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]]; + } - _make_quadrant_dirty(Q); -} + // 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); -Vector2 TileMap::get_cell_autotile_coord(int p_x, int p_y) const { + rs->canvas_item_add_polygon(p_quadrant->debug_canvas_item, vertices, colors); + } + } + } + } + } + } +} - PosKey pk(p_x, p_y); +/////////////////////////////// Scenes ////////////////////////////////////// - const Map<PosKey, Cell>::Element *E = tile_map.find(pk); +void TileMap::_scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { + ERR_FAIL_COND(!tile_set.is_valid()); - if (!E) - return Vector2(); + SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); + while (q_list_element) { + TileMapQuadrant &q = *q_list_element->self(); - return Vector2(E->get().autotile_coord_x, E->get().autotile_coord_y); -} + // 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(); + } + } -void TileMap::_recreate_quadrants() { + q.scenes.clear(); - _clear_quadrants(); + // Recreate the scenes. + for (Set<Vector2i>::Element *E_cell = q.cells.front(); E_cell; E_cell = E_cell->next()) { + const TileMapCell &c = get_cell(q.layer, E_cell->get(), true); - for (Map<PosKey, Cell>::Element *E = tile_map.front(); E; E = E->next()) { + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); - PosKey qk = PosKey(E->key().x, E->key().y).to_quadrant(_get_quadrant_size()); + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } - Map<PosKey, Quadrant>::Element *Q = quadrant_map.find(qk); - if (!Q) { - Q = _create_quadrant(qk); - dirty_quadrant_list.add(&Q->get().dirty_list); + 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->get().cells.insert(E->key()); - _make_quadrant_dirty(Q, false); + q_list_element = q_list_element->next(); } - update_dirty_quadrants(); } -void TileMap::_clear_quadrants() { - - while (quadrant_map.size()) { - _erase_quadrant(quadrant_map.front()); +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(); + } } -} -void TileMap::set_material(const Ref<Material> &p_material) { - - CanvasItem::set_material(p_material); - _update_all_items_material_state(); + p_quadrant->scenes.clear(); } -void TileMap::set_use_parent_material(bool p_use_parent_material) { +void TileMap::_scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { + ERR_FAIL_COND(!tile_set.is_valid()); - CanvasItem::set_use_parent_material(p_use_parent_material); - _update_all_items_material_state(); -} + if (!Engine::get_singleton()->is_editor_hint()) { + return; + } -void TileMap::_update_all_items_material_state() { + // Draw a placeholder for scenes needing one. + RenderingServer *rs = RenderingServer::get_singleton(); + Vector2 quadrant_pos = map_to_world(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer)); + for (Set<Vector2i>::Element *E_cell = p_quadrant->cells.front(); E_cell; E_cell = E_cell->next()) { + const TileMapCell &c = get_cell(p_quadrant->layer, E_cell->get(), true); - for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); - Quadrant &q = E->get(); - for (List<RID>::Element *F = q.canvas_items.front(); F; F = F->next()) { + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } - _update_item_material_state(F->get()); + 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::_update_item_material_state(const RID &p_canvas_item) { - - RS::get_singleton()->canvas_item_set_use_parent_material(p_canvas_item, get_use_parent_material() || get_material().is_valid()); -} - -void TileMap::clear() { +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()); - _clear_quadrants(); - tile_map.clear(); - used_size_cache_dirty = true; -} + // 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); -void TileMap::_set_tile_data(const Vector<int> &p_data) { + int source_id = p_source_id; + Vector2i atlas_coords = p_atlas_coords; + int alternative_tile = p_alternative_tile; - ERR_FAIL_COND(format > FORMAT_2); + 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 = TileSet::INVALID_SOURCE; + atlas_coords = TileSetSource::INVALID_ATLAS_COORDS; + alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; + } - int c = p_data.size(); - const int *r = p_data.ptr(); + if (!E && source_id == TileSet::INVALID_SOURCE) { + return; // Nothing to do, the tile is already empty. + } - int offset = (format == FORMAT_2) ? 3 : 2; + // Get the quadrant + Vector2i qk = _coords_to_quadrant_coords(p_layer, pk); - clear(); - for (int i = 0; i < c; i += offset) { + Map<Vector2i, TileMapQuadrant>::Element *Q = layers[p_layer].quadrant_map.find(qk); - const uint8_t *ptr = (const uint8_t *)&r[i]; - uint8_t local[12]; - for (int j = 0; j < ((format == FORMAT_2) ? 12 : 8); j++) - local[j] = ptr[j]; + if (source_id == TileSet::INVALID_SOURCE) { + // Erase existing cell in the tile map. + tile_map.erase(pk); -#ifdef BIG_ENDIAN_ENABLED + // Erase existing cell in the quadrant. + ERR_FAIL_COND(!Q); + TileMapQuadrant &q = Q->get(); - SWAP(local[0], local[3]); - SWAP(local[1], local[2]); - SWAP(local[4], local[7]); - SWAP(local[5], local[6]); - //TODO: ask someone to check this... - if (FORMAT == FORMAT_2) { - SWAP(local[8], local[11]); - SWAP(local[9], local[10]); - } -#endif + q.cells.erase(pk); - uint16_t x = decode_uint16(&local[0]); - uint16_t y = decode_uint16(&local[2]); - uint32_t v = decode_uint32(&local[4]); - bool flip_h = v & (1 << 29); - bool flip_v = v & (1 << 30); - bool transpose = v & (1 << 31); - v &= (1 << 29) - 1; - int16_t coord_x = 0; - int16_t coord_y = 0; - if (format == FORMAT_2) { - coord_x = decode_uint16(&local[8]); - coord_y = decode_uint16(&local[10]); + // Remove or make the quadrant dirty. + if (q.cells.size() == 0) { + _erase_quadrant(Q); + } else { + _make_quadrant_dirty(Q); } - set_cell(x, y, v, flip_h, flip_v, transpose, Vector2(coord_x, coord_y)); - } -} + used_rect_cache_dirty = true; + } else { + if (!E) { + // Insert a new cell in the tile map. + E = tile_map.insert(pk, TileMapCell()); -Vector<int> TileMap::_get_tile_data() const { + // Create a new quadrant if needed, then insert the cell if needed. + if (!Q) { + Q = _create_quadrant(p_layer, qk); + } + TileMapQuadrant &q = Q->get(); + q.cells.insert(pk); - Vector<int> data; - data.resize(tile_map.size() * 3); - int *w = data.ptrw(); + } else { + ERR_FAIL_COND(!Q); // TileMapQuadrant should exist... - // Save in highest format + if (E->get().source_id == source_id && E->get().get_atlas_coords() == atlas_coords && E->get().alternative_tile == alternative_tile) { + return; // Nothing changed. + } + } - int idx = 0; - for (const Map<PosKey, Cell>::Element *E = tile_map.front(); E; E = E->next()) { - uint8_t *ptr = (uint8_t *)&w[idx]; - encode_uint16(E->key().x, &ptr[0]); - encode_uint16(E->key().y, &ptr[2]); - uint32_t val = E->get().id; - if (E->get().flip_h) - val |= (1 << 29); - if (E->get().flip_v) - val |= (1 << 30); - if (E->get().transpose) - val |= (1 << 31); - encode_uint32(val, &ptr[4]); - encode_uint16(E->get().autotile_coord_x, &ptr[8]); - encode_uint16(E->get().autotile_coord_y, &ptr[10]); - idx += 3; - } + TileMapCell &c = E->get(); - return data; -} + c.source_id = source_id; + c.set_atlas_coords(atlas_coords); + c.alternative_tile = alternative_tile; -#ifdef TOOLS_ENABLED -Rect2 TileMap::_edit_get_rect() const { - if (pending_update) { - const_cast<TileMap *>(this)->update_dirty_quadrants(); - } else { - const_cast<TileMap *>(this)->_recompute_rect_cache(); + _make_quadrant_dirty(Q); + used_rect_cache_dirty = true; } - return rect_cache; } -#endif -void TileMap::set_collision_layer(uint32_t p_layer) { +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); - collision_layer = p_layer; - if (!use_parent) { - for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { + // 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); - Quadrant &q = E->get(); - PhysicsServer2D::get_singleton()->body_set_collision_layer(q.body, collision_layer); - } + if (!E) { + return TileSet::INVALID_SOURCE; } -} - -void TileMap::set_collision_mask(uint32_t p_mask) { - collision_mask = p_mask; - if (!use_parent) { - for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { - - Quadrant &q = E->get(); - PhysicsServer2D::get_singleton()->body_set_collision_mask(q.body, collision_mask); - } + 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]; } -} - -void TileMap::set_collision_layer_bit(int p_bit, bool p_value) { - - uint32_t layer = get_collision_layer(); - if (p_value) - layer |= 1 << p_bit; - else - layer &= ~(1 << p_bit); - set_collision_layer(layer); -} - -void TileMap::set_collision_mask_bit(int p_bit, bool p_value) { - uint32_t mask = get_collision_mask(); - if (p_value) - mask |= 1 << p_bit; - else - mask &= ~(1 << p_bit); - set_collision_mask(mask); + return E->get().source_id; } -bool TileMap::get_collision_use_kinematic() const { - - return use_kinematic; -} +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); -void TileMap::set_collision_use_kinematic(bool p_use_kinematic) { + // 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); - _clear_quadrants(); - use_kinematic = p_use_kinematic; - _recreate_quadrants(); -} + if (!E) { + return TileSetSource::INVALID_ATLAS_COORDS; + } -bool TileMap::get_collision_use_parent() const { + 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 use_parent; + return E->get().get_atlas_coords(); } -void TileMap::set_collision_use_parent(bool p_use_parent) { +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); - if (use_parent == p_use_parent) return; + // 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); - _clear_quadrants(); - - use_parent = p_use_parent; - set_notify_local_transform(use_parent); + if (!E) { + return TileSetSource::INVALID_TILE_ALTERNATIVE; + } - if (use_parent && is_inside_tree()) { - collision_parent = Object::cast_to<CollisionObject2D>(get_parent()); - } else { - collision_parent = nullptr; + 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]; } - _recreate_quadrants(); - _change_notify(); - update_configuration_warning(); + return E->get().alternative_tile; } -void TileMap::set_collision_friction(float p_friction) { +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); - friction = p_friction; - if (!use_parent) { - for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { + TileMapPattern *output = memnew(TileMapPattern); + if (p_coords_array.is_empty()) { + return output; + } + + Vector2i min = Vector2i(p_coords_array[0]); + for (int i = 1; i < p_coords_array.size(); i++) { + min = min.min(p_coords_array[i]); + } - Quadrant &q = E->get(); - PhysicsServer2D::get_singleton()->body_set_param(q.body, PhysicsServer2D::BODY_PARAM_FRICTION, p_friction); + Vector<Vector2i> coords_in_pattern_array; + coords_in_pattern_array.resize(p_coords_array.size()); + Vector2i ensure_positive_offset; + for (int i = 0; i < p_coords_array.size(); i++) { + Vector2i coords = p_coords_array[i]; + Vector2i coords_in_pattern = coords - min; + if (tile_set->get_tile_shape() != TileSet::TILE_SHAPE_SQUARE) { + if (tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_STACKED) { + if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL && bool(min.y % 2) && bool(coords_in_pattern.y % 2)) { + coords_in_pattern.x -= 1; + if (coords_in_pattern.x < 0) { + ensure_positive_offset.x = 1; + } + } else if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(min.x % 2) && bool(coords_in_pattern.x % 2)) { + coords_in_pattern.y -= 1; + if (coords_in_pattern.y < 0) { + ensure_positive_offset.y = 1; + } + } + } else if (tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_STACKED_OFFSET) { + if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL && bool(min.y % 2) && bool(coords_in_pattern.y % 2)) { + coords_in_pattern.x += 1; + } else if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(min.x % 2) && bool(coords_in_pattern.x % 2)) { + coords_in_pattern.y += 1; + } + } } + coords_in_pattern_array.write[i] = coords_in_pattern; } -} -float TileMap::get_collision_friction() const { + 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(p_layer, coords), get_cell_atlas_coords(p_layer, coords), get_cell_alternative_tile(p_layer, coords)); + } - return friction; + return output; } -void TileMap::set_collision_bounce(float p_bounce) { +Vector2i TileMap::map_pattern(Vector2i p_position_in_tilemap, Vector2i p_coords_in_pattern, const TileMapPattern *p_pattern) { + ERR_FAIL_COND_V(!p_pattern->has_cell(p_coords_in_pattern), Vector2i()); - bounce = p_bounce; - if (!use_parent) { - for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { - - Quadrant &q = E->get(); - PhysicsServer2D::get_singleton()->body_set_param(q.body, PhysicsServer2D::BODY_PARAM_BOUNCE, p_bounce); + Vector2i output = p_position_in_tilemap + p_coords_in_pattern; + if (tile_set->get_tile_shape() != TileSet::TILE_SHAPE_SQUARE) { + if (tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_STACKED) { + if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL && bool(p_position_in_tilemap.y % 2) && bool(p_coords_in_pattern.y % 2)) { + output.x += 1; + } else if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(p_position_in_tilemap.x % 2) && bool(p_coords_in_pattern.x % 2)) { + output.y += 1; + } + } else if (tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_STACKED_OFFSET) { + if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL && bool(p_position_in_tilemap.y % 2) && bool(p_coords_in_pattern.y % 2)) { + output.x -= 1; + } else if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(p_position_in_tilemap.x % 2) && bool(p_coords_in_pattern.x % 2)) { + output.y -= 1; + } } } -} -float TileMap::get_collision_bounce() const { - return bounce; + return output; } -uint32_t TileMap::get_collision_layer() const { +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()); - return collision_layer; + TypedArray<Vector2i> used_cells = p_pattern->get_used_cells(); + for (int i = 0; i < used_cells.size(); i++) { + Vector2i coords = map_pattern(p_position, used_cells[i], p_pattern); + set_cell(p_layer, coords, p_pattern->get_cell_source_id(coords), p_pattern->get_cell_atlas_coords(coords), p_pattern->get_cell_alternative_tile(coords)); + } } -uint32_t TileMap::get_collision_mask() const { - - return collision_mask; +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 { + 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; + } } -bool TileMap::get_collision_layer_bit(int p_bit) const { +Map<Vector2i, TileMapQuadrant> *TileMap::get_quadrant_map(int p_layer) { + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), nullptr); - return get_collision_layer() & (1 << p_bit); + return &layers[p_layer].quadrant_map; } -bool TileMap::get_collision_mask_bit(int p_bit) const { +void TileMap::fix_invalid_tiles() { + ERR_FAIL_COND_MSG(tile_set.is_null(), "Cannot fix invalid tiles if Tileset is not open."); - return get_collision_mask() & (1 << p_bit); + 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(i, E->get(), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); + } + } } -void TileMap::set_mode(Mode p_mode) { +void TileMap::clear_layer(int p_layer) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); - _clear_quadrants(); - mode = p_mode; - _recreate_quadrants(); - emit_signal("settings_changed"); -} + // Remove all tiles. + _clear_layer_internals(p_layer); + layers[p_layer].tile_map.clear(); -TileMap::Mode TileMap::get_mode() const { - return mode; + used_rect_cache_dirty = true; } -void TileMap::set_half_offset(HalfOffset p_half_offset) { - - _clear_quadrants(); - half_offset = p_half_offset; - _recreate_quadrants(); - emit_signal("settings_changed"); +void TileMap::clear() { + // Remove all tiles. + _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_origin(TileOrigin p_tile_origin) { +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); - _clear_quadrants(); - tile_origin = p_tile_origin; - _recreate_quadrants(); - emit_signal("settings_changed"); -} + // Set data for a given tile from raw data. -TileMap::TileOrigin TileMap::get_tile_origin() const { + int c = p_data.size(); + const int *r = p_data.ptr(); - return tile_origin; -} + int offset = (format >= FORMAT_2) ? 3 : 2; + ERR_FAIL_COND_MSG(c % offset != 0, "Corrupted tile data."); -Vector2 TileMap::get_cell_draw_offset() const { + clear_layer(p_layer); - switch (mode) { +#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)); +#endif - case MODE_SQUARE: { + for (int i = 0; i < c; i += offset) { + const uint8_t *ptr = (const uint8_t *)&r[i]; + uint8_t local[12]; + for (int j = 0; j < ((format >= FORMAT_2) ? 12 : 8); j++) { + local[j] = ptr[j]; + } - return Vector2(); - } break; - case MODE_ISOMETRIC: { +#ifdef BIG_ENDIAN_ENABLED - return Vector2(-cell_size.x * 0.5, 0); + SWAP(local[0], local[3]); + SWAP(local[1], local[2]); + SWAP(local[4], local[7]); + SWAP(local[5], local[6]); + //TODO: ask someone to check this... + if (FORMAT >= FORMAT_2) { + SWAP(local[8], local[11]); + 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_uint16(&local[8]); + uint16_t alternative_tile = decode_uint16(&local[10]); + set_cell(p_layer, Vector2i(x, y), source_id, Vector2i(atlas_coords_x, atlas_coords_y), alternative_tile); + } else { +#ifndef DISABLE_DEPRECATED + // Previous decated format. + + 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) { + coord_x = decode_uint16(&local[8]); + coord_y = decode_uint16(&local[10]); + } - } break; - case MODE_CUSTOM: { - - Vector2 min; - min.x = MIN(custom_transform[0].x, min.x); - min.y = MIN(custom_transform[0].y, min.y); - min.x = MIN(custom_transform[1].x, min.x); - min.y = MIN(custom_transform[1].y, min.y); - return min; - } break; + if (tile_set.is_valid()) { + 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); + } +#endif + } } - - return Vector2(); + emit_signal(SNAME("changed")); } -TileMap::HalfOffset TileMap::get_half_offset() const { - return half_offset; -} - -Transform2D TileMap::get_cell_transform() const { - - switch (mode) { - - case MODE_SQUARE: { +Vector<int> TileMap::_get_tile_data(int p_layer) const { + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), Vector<int>()); - Transform2D m; - m[0] *= cell_size.x; - m[1] *= cell_size.y; - return m; - } break; - case MODE_ISOMETRIC: { - - //isometric only makes sense when y is positive in both x and y vectors, otherwise - //the drawing of tiles will overlap - Transform2D m; - m[0] = Vector2(cell_size.x * 0.5, cell_size.y * 0.5); - m[1] = Vector2(-cell_size.x * 0.5, cell_size.y * 0.5); - return m; + // 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(); - } break; - case MODE_CUSTOM: { + // Save in highest format - return custom_transform; - } break; + int idx = 0; + for (const Map<Vector2i, TileMapCell>::Element *E = tile_map.front(); E; E = E->next()) { + uint8_t *ptr = (uint8_t *)&w[idx]; + encode_uint16((int16_t)(E->key().x), &ptr[0]); + encode_uint16((int16_t)(E->key().y), &ptr[2]); + encode_uint16(E->get().source_id, &ptr[4]); + encode_uint16(E->get().coord_x, &ptr[6]); + encode_uint16(E->get().coord_y, &ptr[8]); + encode_uint16(E->get().alternative_tile, &ptr[10]); + idx += 3; } - return Transform2D(); -} - -void TileMap::set_custom_transform(const Transform2D &p_xform) { - - _clear_quadrants(); - custom_transform = p_xform; - _recreate_quadrants(); - emit_signal("settings_changed"); -} - -Transform2D TileMap::get_custom_transform() const { - - return custom_transform; + return data; } -Vector2 TileMap::_map_to_world(int p_x, int p_y, bool p_ignore_ofs) const { - - Vector2 ret = get_cell_transform().xform(Vector2(p_x, p_y)); - if (!p_ignore_ofs) { - switch (half_offset) { - - case HALF_OFFSET_X: - case HALF_OFFSET_NEGATIVE_X: { - if (ABS(p_y) & 1) { - - ret += get_cell_transform()[0] * (half_offset == HALF_OFFSET_X ? 0.5 : -0.5); - } - } break; - case HALF_OFFSET_Y: - case HALF_OFFSET_NEGATIVE_Y: { - if (ABS(p_x) & 1) { - ret += get_cell_transform()[1] * (half_offset == HALF_OFFSET_Y ? 0.5 : -0.5); - } - } break; - case HALF_OFFSET_DISABLED: { - // Nothing to do. - } - } +#ifdef TOOLS_ENABLED +Rect2 TileMap::_edit_get_rect() const { + // Return the visible rect of the tilemap + if (pending_update) { + const_cast<TileMap *>(this)->_update_dirty_quadrants(); + } else { + const_cast<TileMap *>(this)->_recompute_rect_cache(); } - return ret; + return rect_cache; } +#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_2; // When saving, always save highest format - return true; - } else if (p_name == "tile_data") { - r_ret = _get_tile_data(); + r_ret = FORMAT_3; // When saving, always save highest format 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); -} - -void TileMap::_validate_property(PropertyInfo &property) const { - if (use_parent && property.name != "collision_use_parent" && property.name.begins_with("collision_")) { - property.usage = PROPERTY_USAGE_NOEDITOR; + 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 Vector2 &p_pos, bool p_ignore_ofs) const { - - return _map_to_world(p_pos.x, p_pos.y, p_ignore_ofs); -} - -Vector2 TileMap::world_to_map(const Vector2 &p_pos) const { +Vector2 TileMap::map_to_world(const Vector2i &p_pos) const { + // SHOULD RETURN THE CENTER OF THE TILE + ERR_FAIL_COND_V(!tile_set.is_valid(), Vector2()); - Vector2 ret = get_cell_transform().affine_inverse().xform(p_pos); + Vector2 ret = p_pos; + TileSet::TileShape tile_shape = tile_set->get_tile_shape(); + TileSet::TileOffsetAxis tile_offset_axis = tile_set->get_tile_offset_axis(); - switch (half_offset) { - - case HALF_OFFSET_X: { - if (int(floor(ret.y)) & 1) { - ret.x -= 0.5; - } - } break; - case HALF_OFFSET_NEGATIVE_X: { - if (int(floor(ret.y)) & 1) { - ret.x += 0.5; - } - } break; - case HALF_OFFSET_Y: { - if (int(floor(ret.x)) & 1) { - ret.y -= 0.5; + if (tile_shape == TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE || tile_shape == TileSet::TILE_SHAPE_HEXAGON || tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + // Technically, those 3 shapes are equivalent, as they are basically half-offset, but with different levels or overlap. + // square = no overlap, hexagon = 0.25 overlap, isometric = 0.5 overlap + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + switch (tile_set->get_tile_layout()) { + case TileSet::TILE_LAYOUT_STACKED: + ret = Vector2(ret.x + (Math::posmod(ret.y, 2) == 0 ? 0.0 : 0.5), ret.y); + break; + case TileSet::TILE_LAYOUT_STACKED_OFFSET: + ret = Vector2(ret.x + (Math::posmod(ret.y, 2) == 1 ? 0.0 : 0.5), ret.y); + break; + case TileSet::TILE_LAYOUT_STAIRS_RIGHT: + ret = Vector2(ret.x + ret.y / 2, ret.y); + break; + case TileSet::TILE_LAYOUT_STAIRS_DOWN: + ret = Vector2(ret.x / 2, ret.y * 2 + ret.x); + break; + case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: + ret = Vector2((ret.x + ret.y) / 2, ret.y - ret.x); + break; + case TileSet::TILE_LAYOUT_DIAMOND_DOWN: + ret = Vector2((ret.x - ret.y) / 2, ret.y + ret.x); + break; } - } break; - case HALF_OFFSET_NEGATIVE_Y: { - if (int(floor(ret.x)) & 1) { - ret.y += 0.5; + } else { // TILE_OFFSET_AXIS_VERTICAL + switch (tile_set->get_tile_layout()) { + case TileSet::TILE_LAYOUT_STACKED: + ret = Vector2(ret.x, ret.y + (Math::posmod(ret.x, 2) == 0 ? 0.0 : 0.5)); + break; + case TileSet::TILE_LAYOUT_STACKED_OFFSET: + ret = Vector2(ret.x, ret.y + (Math::posmod(ret.x, 2) == 1 ? 0.0 : 0.5)); + break; + case TileSet::TILE_LAYOUT_STAIRS_RIGHT: + ret = Vector2(ret.x * 2 + ret.y, ret.y / 2); + break; + case TileSet::TILE_LAYOUT_STAIRS_DOWN: + ret = Vector2(ret.x, ret.y + ret.x / 2); + break; + case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: + ret = Vector2(ret.x + ret.y, (ret.y - ret.x) / 2); + break; + case TileSet::TILE_LAYOUT_DIAMOND_DOWN: + ret = Vector2(ret.x - ret.y, (ret.y + ret.x) / 2); + break; } - } break; - case HALF_OFFSET_DISABLED: { - // Nothing to do. } } - // Account for precision errors on the border (GH-23250). - // 0.00005 is 5*CMP_EPSILON, results would start being unpredictable if - // cell size is > 15,000, but we can hardly have more precision anyway with - // floating point. - ret += Vector2(0.00005, 0.00005); - return ret.floor(); -} - -void TileMap::set_y_sort_mode(bool p_enable) { + // Multiply by the overlapping ratio + double overlapping_ratio = 1.0; + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + overlapping_ratio = 0.5; + } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) { + overlapping_ratio = 0.75; + } + ret.y *= overlapping_ratio; + } else { // TILE_OFFSET_AXIS_VERTICAL + if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + overlapping_ratio = 0.5; + } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) { + overlapping_ratio = 0.75; + } + ret.x *= overlapping_ratio; + } - _clear_quadrants(); - y_sort_mode = p_enable; - RS::get_singleton()->canvas_item_set_sort_children_by_y(get_canvas_item(), y_sort_mode); - _recreate_quadrants(); - emit_signal("settings_changed"); + return (ret + Vector2(0.5, 0.5)) * tile_set->get_tile_size(); } -bool TileMap::is_y_sort_mode_enabled() const { +Vector2i TileMap::world_to_map(const Vector2 &p_pos) const { + ERR_FAIL_COND_V(!tile_set.is_valid(), Vector2i()); - return y_sort_mode; -} + Vector2 ret = p_pos; + ret /= tile_set->get_tile_size(); -void TileMap::set_compatibility_mode(bool p_enable) { + TileSet::TileShape tile_shape = tile_set->get_tile_shape(); + TileSet::TileOffsetAxis tile_offset_axis = tile_set->get_tile_offset_axis(); + TileSet::TileLayout tile_layout = tile_set->get_tile_layout(); - _clear_quadrants(); - compatibility_mode = p_enable; - _recreate_quadrants(); - emit_signal("settings_changed"); -} + // Divide by the overlapping ratio + double overlapping_ratio = 1.0; + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + overlapping_ratio = 0.5; + } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) { + overlapping_ratio = 0.75; + } + ret.y /= overlapping_ratio; + } else { // TILE_OFFSET_AXIS_VERTICAL + if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + overlapping_ratio = 0.5; + } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) { + overlapping_ratio = 0.75; + } + ret.x /= overlapping_ratio; + } + + // For each half-offset shape, we check if we are in the corner of the tile, and thus should correct the world position accordingly. + if (tile_shape == TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE || tile_shape == TileSet::TILE_SHAPE_HEXAGON || tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + // Technically, those 3 shapes are equivalent, as they are basically half-offset, but with different levels or overlap. + // square = no overlap, hexagon = 0.25 overlap, isometric = 0.5 overlap + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + // Smart floor of the position + Vector2 raw_pos = ret; + if (Math::posmod(Math::floor(ret.y), 2) ^ (tile_layout == TileSet::TILE_LAYOUT_STACKED_OFFSET)) { + ret = Vector2(Math::floor(ret.x + 0.5) - 0.5, Math::floor(ret.y)); + } else { + ret = ret.floor(); + } -bool TileMap::is_compatibility_mode_enabled() const { + // Compute the tile offset, and if we might the output for a neighbour top tile + Vector2 in_tile_pos = raw_pos - ret; + bool in_top_left_triangle = (in_tile_pos - Vector2(0.5, 0.0)).cross(Vector2(-0.5, 1.0 / overlapping_ratio - 1)) <= 0; + bool in_top_right_triangle = (in_tile_pos - Vector2(0.5, 0.0)).cross(Vector2(0.5, 1.0 / overlapping_ratio - 1)) > 0; + + switch (tile_layout) { + case TileSet::TILE_LAYOUT_STACKED: + ret = ret.floor(); + if (in_top_left_triangle) { + ret += Vector2i(Math::posmod(Math::floor(ret.y), 2) ? 0 : -1, -1); + } else if (in_top_right_triangle) { + ret += Vector2i(Math::posmod(Math::floor(ret.y), 2) ? 1 : 0, -1); + } + break; + case TileSet::TILE_LAYOUT_STACKED_OFFSET: + ret = ret.floor(); + if (in_top_left_triangle) { + ret += Vector2i(Math::posmod(Math::floor(ret.y), 2) ? -1 : 0, -1); + } else if (in_top_right_triangle) { + ret += Vector2i(Math::posmod(Math::floor(ret.y), 2) ? 0 : 1, -1); + } + break; + case TileSet::TILE_LAYOUT_STAIRS_RIGHT: + ret = Vector2(ret.x - ret.y / 2, ret.y).floor(); + if (in_top_left_triangle) { + ret += Vector2i(0, -1); + } else if (in_top_right_triangle) { + ret += Vector2i(1, -1); + } + break; + case TileSet::TILE_LAYOUT_STAIRS_DOWN: + ret = Vector2(ret.x * 2, ret.y / 2 - ret.x).floor(); + if (in_top_left_triangle) { + ret += Vector2i(-1, 0); + } else if (in_top_right_triangle) { + ret += Vector2i(1, -1); + } + break; + case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: + ret = Vector2(ret.x - ret.y / 2, ret.y / 2 + ret.x).floor(); + if (in_top_left_triangle) { + ret += Vector2i(0, -1); + } else if (in_top_right_triangle) { + ret += Vector2i(1, 0); + } + break; + case TileSet::TILE_LAYOUT_DIAMOND_DOWN: + ret = Vector2(ret.x + ret.y / 2, ret.y / 2 - ret.x).floor(); + if (in_top_left_triangle) { + ret += Vector2i(-1, 0); + } else if (in_top_right_triangle) { + ret += Vector2i(0, -1); + } + break; + } + } else { // TILE_OFFSET_AXIS_VERTICAL + // Smart floor of the position + Vector2 raw_pos = ret; + if (Math::posmod(Math::floor(ret.x), 2) ^ (tile_layout == TileSet::TILE_LAYOUT_STACKED_OFFSET)) { + ret = Vector2(Math::floor(ret.x), Math::floor(ret.y + 0.5) - 0.5); + } else { + ret = ret.floor(); + } - return compatibility_mode; + // Compute the tile offset, and if we might the output for a neighbour top tile + Vector2 in_tile_pos = raw_pos - ret; + bool in_top_left_triangle = (in_tile_pos - Vector2(0.0, 0.5)).cross(Vector2(1.0 / overlapping_ratio - 1, -0.5)) > 0; + bool in_bottom_left_triangle = (in_tile_pos - Vector2(0.0, 0.5)).cross(Vector2(1.0 / overlapping_ratio - 1, 0.5)) <= 0; + + switch (tile_layout) { + case TileSet::TILE_LAYOUT_STACKED: + ret = ret.floor(); + if (in_top_left_triangle) { + ret += Vector2i(-1, Math::posmod(Math::floor(ret.x), 2) ? 0 : -1); + } else if (in_bottom_left_triangle) { + ret += Vector2i(-1, Math::posmod(Math::floor(ret.x), 2) ? 1 : 0); + } + break; + case TileSet::TILE_LAYOUT_STACKED_OFFSET: + ret = ret.floor(); + if (in_top_left_triangle) { + ret += Vector2i(-1, Math::posmod(Math::floor(ret.x), 2) ? -1 : 0); + } else if (in_bottom_left_triangle) { + ret += Vector2i(-1, Math::posmod(Math::floor(ret.x), 2) ? 0 : 1); + } + break; + case TileSet::TILE_LAYOUT_STAIRS_RIGHT: + ret = Vector2(ret.x / 2 - ret.y, ret.y * 2).floor(); + if (in_top_left_triangle) { + ret += Vector2i(0, -1); + } else if (in_bottom_left_triangle) { + ret += Vector2i(-1, 1); + } + break; + case TileSet::TILE_LAYOUT_STAIRS_DOWN: + ret = Vector2(ret.x, ret.y - ret.x / 2).floor(); + if (in_top_left_triangle) { + ret += Vector2i(-1, 0); + } else if (in_bottom_left_triangle) { + ret += Vector2i(-1, 1); + } + break; + case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: + ret = Vector2(ret.x / 2 - ret.y, ret.y + ret.x / 2).floor(); + if (in_top_left_triangle) { + ret += Vector2i(0, -1); + } else if (in_bottom_left_triangle) { + ret += Vector2i(-1, 0); + } + break; + case TileSet::TILE_LAYOUT_DIAMOND_DOWN: + ret = Vector2(ret.x / 2 + ret.y, ret.y - ret.x / 2).floor(); + if (in_top_left_triangle) { + ret += Vector2i(-1, 0); + } else if (in_bottom_left_triangle) { + ret += Vector2i(0, 1); + } + break; + } + } + } else { + ret = (ret + Vector2(0.00005, 0.00005)).floor(); + } + return Vector2i(ret); +} + +bool TileMap::is_existing_neighbor(TileSet::CellNeighbor p_cell_neighbor) const { + ERR_FAIL_COND_V(!tile_set.is_valid(), false); + + TileSet::TileShape shape = tile_set->get_tile_shape(); + if (shape == TileSet::TILE_SHAPE_SQUARE) { + return p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER; + + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) { + return p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; + } else { + if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + return p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; + } else { + return p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; + } + } } -void TileMap::set_centered_textures(bool p_enable) { +Vector2i TileMap::get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeighbor p_cell_neighbor) const { + ERR_FAIL_COND_V(!tile_set.is_valid(), p_coords); + + TileSet::TileShape shape = tile_set->get_tile_shape(); + if (shape == TileSet::TILE_SHAPE_SQUARE) { + switch (p_cell_neighbor) { + case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: + return p_coords + Vector2i(1, 0); + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + return p_coords + Vector2i(1, 1); + case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: + return p_coords + Vector2i(0, 1); + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + return p_coords + Vector2i(-1, 1); + case TileSet::CELL_NEIGHBOR_LEFT_SIDE: + return p_coords + Vector2i(-1, 0); + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + return p_coords + Vector2i(-1, -1); + case TileSet::CELL_NEIGHBOR_TOP_SIDE: + return p_coords + Vector2i(0, -1); + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + return p_coords + Vector2i(1, -1); + default: + ERR_FAIL_V(p_coords); + } + } else { // Half-offset shapes (square and hexagon) + switch (tile_set->get_tile_layout()) { + case TileSet::TILE_LAYOUT_STACKED: { + if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + bool is_offset = p_coords.y % 2; + if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || + (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { + return p_coords + Vector2i(1, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(is_offset ? 1 : 0, 1); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { + return p_coords + Vector2i(0, 2); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(is_offset ? 0 : -1, 1); + } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || + (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { + return p_coords + Vector2i(-1, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(is_offset ? 0 : -1, -1); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { + return p_coords + Vector2i(0, -2); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(is_offset ? 1 : 0, -1); + } else { + ERR_FAIL_V(p_coords); + } + } else { + bool is_offset = p_coords.x % 2; + + if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || + (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { + return p_coords + Vector2i(0, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(1, is_offset ? 1 : 0); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { + return p_coords + Vector2i(2, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(1, is_offset ? 0 : -1); + } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || + (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { + return p_coords + Vector2i(0, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(-1, is_offset ? 0 : -1); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { + return p_coords + Vector2i(-2, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(-1, is_offset ? 1 : 0); + } else { + ERR_FAIL_V(p_coords); + } + } + } break; + case TileSet::TILE_LAYOUT_STACKED_OFFSET: { + if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + bool is_offset = p_coords.y % 2; + + if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || + (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { + return p_coords + Vector2i(1, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(is_offset ? 0 : 1, 1); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { + return p_coords + Vector2i(0, 2); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(is_offset ? -1 : 0, 1); + } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || + (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { + return p_coords + Vector2i(-1, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(is_offset ? -1 : 0, -1); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { + return p_coords + Vector2i(0, -2); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(is_offset ? 0 : 1, -1); + } else { + ERR_FAIL_V(p_coords); + } + } else { + bool is_offset = p_coords.x % 2; + + if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || + (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { + return p_coords + Vector2i(0, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(1, is_offset ? 0 : 1); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { + return p_coords + Vector2i(2, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(1, is_offset ? -1 : 0); + } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || + (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { + return p_coords + Vector2i(0, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(-1, is_offset ? -1 : 0); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { + return p_coords + Vector2i(-2, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(-1, is_offset ? 0 : 1); + } else { + ERR_FAIL_V(p_coords); + } + } + } break; + case TileSet::TILE_LAYOUT_STAIRS_RIGHT: + case TileSet::TILE_LAYOUT_STAIRS_DOWN: { + if ((tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_STAIRS_RIGHT) ^ (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { + if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || + (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { + return p_coords + Vector2i(1, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(0, 1); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { + return p_coords + Vector2i(-1, 2); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(-1, 1); + } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || + (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { + return p_coords + Vector2i(-1, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(0, -1); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { + return p_coords + Vector2i(1, -2); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(1, -1); + } else { + ERR_FAIL_V(p_coords); + } - _clear_quadrants(); - centered_textures = p_enable; - _recreate_quadrants(); - emit_signal("settings_changed"); -} + } else { + if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || + (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { + return p_coords + Vector2i(0, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(1, 0); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { + return p_coords + Vector2i(2, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(1, -1); + } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || + (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { + return p_coords + Vector2i(0, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(-1, 0); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { + return p_coords + Vector2i(-2, 1); + + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(-1, 1); + } else { + ERR_FAIL_V(p_coords); + } + } + } else { + if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || + (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { + return p_coords + Vector2i(2, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(1, 0); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { + return p_coords + Vector2i(0, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(-1, 1); + } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || + (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { + return p_coords + Vector2i(-2, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(-1, 0); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { + return p_coords + Vector2i(0, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(1, -1); + } else { + ERR_FAIL_V(p_coords); + } -bool TileMap::is_centered_textures_enabled() const { + } else { + if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || + (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { + return p_coords + Vector2i(-1, 2); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(0, 1); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { + return p_coords + Vector2i(1, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(1, -1); + } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || + (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { + return p_coords + Vector2i(1, -2); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(0, -1); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { + return p_coords + Vector2i(-1, 0); + + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(-1, 1); + } else { + ERR_FAIL_V(p_coords); + } + } + } + } break; + case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: + case TileSet::TILE_LAYOUT_DIAMOND_DOWN: { + if ((tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_DIAMOND_RIGHT) ^ (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { + if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || + (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { + return p_coords + Vector2i(1, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(0, 1); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { + return p_coords + Vector2i(-1, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(-1, 0); + } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || + (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { + return p_coords + Vector2i(-1, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(0, -1); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { + return p_coords + Vector2i(1, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(1, 0); + } else { + ERR_FAIL_V(p_coords); + } - return centered_textures; + } else { + if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || + (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { + return p_coords + Vector2i(1, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(1, 0); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { + return p_coords + Vector2i(1, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(0, -1); + } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || + (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { + return p_coords + Vector2i(-1, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(-1, 0); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { + return p_coords + Vector2i(-1, 1); + + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(0, 1); + } else { + ERR_FAIL_V(p_coords); + } + } + } else { + if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || + (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { + return p_coords + Vector2i(1, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(1, 0); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { + return p_coords + Vector2i(1, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(0, 1); + } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || + (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { + return p_coords + Vector2i(-1, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(-1, 0); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { + return p_coords + Vector2i(-1, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(0, -1); + } else { + ERR_FAIL_V(p_coords); + } + + } else { + if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || + (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { + return p_coords + Vector2i(-1, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(0, 1); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { + return p_coords + Vector2i(1, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(1, 0); + } else if ((shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || + (shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { + return p_coords + Vector2i(1, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(0, -1); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { + return p_coords + Vector2i(-1, -1); + + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(-1, 0); + } else { + ERR_FAIL_V(p_coords); + } + } + } + } break; + default: + ERR_FAIL_V(p_coords); + } + } } -Array 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>()); - Array a; - a.resize(tile_map.size()); + // Returns the cells used in the tilemap. + TypedArray<Vector2i> a; + a.resize(layers[p_layer].tile_map.size()); int i = 0; - for (Map<PosKey, Cell>::Element *E = tile_map.front(); E; E = E->next()) { - - Vector2 p(E->key().x, E->key().y); + 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; } return a; } -Array TileMap::get_used_cells_by_id(int p_id) const { +Rect2 TileMap::get_used_rect() { // Not const because of cache + // Return the rect of the currently used area + 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; + } - Array a; - for (Map<PosKey, Cell>::Element *E = tile_map.front(); E; E = E->next()) { + 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)); + } + } + } - if (E->value().id == p_id) { - Vector2 p(E->key().x, E->key().y); - a.push_back(p); + 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 a; + return used_rect_cache; } -Rect2 TileMap::get_used_rect() { // Not const because of cache - - 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); +// --- Override some methods of the CanvasItem class to pass the changes to the quadrants CanvasItems --- - for (Map<PosKey, Cell>::Element *E = tile_map.front(); E; E = E->next()) { - used_size_cache.expand_to(Vector2(E->key().x, E->key().y)); +void TileMap::set_light_mask(int p_light_mask) { + // Occlusion: set light mask. + CanvasItem::set_light_mask(p_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()); } - - used_size_cache.size += Vector2(1, 1); - } else { - used_size_cache = Rect2(); } - - used_size_cache_dirty = false; + _rendering_update_layer(layer); } - - return used_size_cache; } -void TileMap::set_occluder_light_mask(int p_mask) { - - occluder_light_mask = p_mask; - for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { +void TileMap::set_material(const Ref<Material> &p_material) { + // Set material for the whole tilemap. + CanvasItem::set_material(p_material); - for (Map<PosKey, Quadrant::Occluder>::Element *F = E->get().occluder_instances.front(); F; F = F->next()) { - RenderingServer::get_singleton()->canvas_light_occluder_set_light_mask(F->get().id, occluder_light_mask); + // Update material for the whole tilemap. + 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); } } -int TileMap::get_occluder_light_mask() const { +void TileMap::set_use_parent_material(bool p_use_parent_material) { + // Set use_parent_material for the whole tilemap. + CanvasItem::set_use_parent_material(p_use_parent_material); - return occluder_light_mask; + // Update use_parent_material for the whole tilemap. + 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_light_mask(int p_light_mask) { - - CanvasItem::set_light_mask(p_light_mask); - for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { +void TileMap::set_texture_filter(TextureFilter p_texture_filter) { + // Set a default texture filter for the whole tilemap + CanvasItem::set_texture_filter(p_texture_filter); + for (unsigned int layer = 0; layer < layers.size(); layer++) { + for (Map<Vector2i, TileMapQuadrant>::Element *F = layers[layer].quadrant_map.front(); F; F = F->next()) { + TileMapQuadrant &q = F->get(); + for (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); + } +} - 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()); +void TileMap::set_texture_repeat(CanvasItem::TextureRepeat p_texture_repeat) { + // Set a default texture repeat for the whole tilemap + CanvasItem::set_texture_repeat(p_texture_repeat); + for (unsigned int layer = 0; layer < layers.size(); layer++) { + for (Map<Vector2i, TileMapQuadrant>::Element *F = layers[layer].quadrant_map.front(); F; F = F->next()) { + TileMapQuadrant &q = F->get(); + for (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); } } -void TileMap::set_clip_uv(bool p_enable) { +TypedArray<Vector2i> TileMap::get_surrounding_tiles(Vector2i coords) { + if (!tile_set.is_valid()) { + return TypedArray<Vector2i>(); + } - if (clip_uv == p_enable) - return; + TypedArray<Vector2i> around; + TileSet::TileShape shape = tile_set->get_tile_shape(); + if (shape == TileSet::TILE_SHAPE_SQUARE) { + around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)); + around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)); + around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_LEFT_SIDE)); + around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_TOP_SIDE)); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) { + around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)); + around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)); + around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE)); + around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)); + } else { + if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)); + around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)); + around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)); + around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_LEFT_SIDE)); + around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE)); + around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)); + } else { + around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)); + around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)); + around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)); + around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE)); + around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_TOP_SIDE)); + around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)); + } + } - _clear_quadrants(); - clip_uv = p_enable; - _recreate_quadrants(); + return around; } -bool TileMap::get_clip_uv() const { +void TileMap::draw_cells_outline(Control *p_control, Set<Vector2i> p_cells, Color p_color, Transform2D p_transform) { + if (!tile_set.is_valid()) { + return; + } - return clip_uv; + // Create a set. + Vector2i tile_size = tile_set->get_tile_size(); + Vector<Vector2> polygon = tile_set->get_tile_shape_polygon(); + TileSet::TileShape shape = tile_set->get_tile_shape(); + + for (Set<Vector2i>::Element *E = p_cells.front(); E; E = E->next()) { + Vector2 center = map_to_world(E->get()); + +#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 } -String TileMap::get_configuration_warning() const { +TypedArray<String> TileMap::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); - String warning = Node2D::get_configuration_warning(); + // 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); + } + } - if (use_parent && !collision_parent) { - if (!warning.empty()) { - warning += "\n\n"; + // 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 TTR("TileMap with Use Parent on needs a parent CollisionObject2D to give shapes to. Please use it as a child of Area2D, StaticBody2D, RigidBody2D, KinematicBody2D, etc. to give them a shape."); } - return warning; + return warnings; } void TileMap::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_tileset", "tileset"), &TileMap::set_tileset); ClassDB::bind_method(D_METHOD("get_tileset"), &TileMap::get_tileset); - ClassDB::bind_method(D_METHOD("set_mode", "mode"), &TileMap::set_mode); - ClassDB::bind_method(D_METHOD("get_mode"), &TileMap::get_mode); - - ClassDB::bind_method(D_METHOD("set_half_offset", "half_offset"), &TileMap::set_half_offset); - ClassDB::bind_method(D_METHOD("get_half_offset"), &TileMap::get_half_offset); - - ClassDB::bind_method(D_METHOD("set_custom_transform", "custom_transform"), &TileMap::set_custom_transform); - ClassDB::bind_method(D_METHOD("get_custom_transform"), &TileMap::get_custom_transform); - - ClassDB::bind_method(D_METHOD("set_cell_size", "size"), &TileMap::set_cell_size); - ClassDB::bind_method(D_METHOD("get_cell_size"), &TileMap::get_cell_size); - - ClassDB::bind_method(D_METHOD("_set_old_cell_size", "size"), &TileMap::_set_old_cell_size); - ClassDB::bind_method(D_METHOD("_get_old_cell_size"), &TileMap::_get_old_cell_size); - 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_tile_origin", "origin"), &TileMap::set_tile_origin); - ClassDB::bind_method(D_METHOD("get_tile_origin"), &TileMap::get_tile_origin); - - ClassDB::bind_method(D_METHOD("set_clip_uv", "enable"), &TileMap::set_clip_uv); - ClassDB::bind_method(D_METHOD("get_clip_uv"), &TileMap::get_clip_uv); - - ClassDB::bind_method(D_METHOD("set_y_sort_mode", "enable"), &TileMap::set_y_sort_mode); - ClassDB::bind_method(D_METHOD("is_y_sort_mode_enabled"), &TileMap::is_y_sort_mode_enabled); - - ClassDB::bind_method(D_METHOD("set_compatibility_mode", "enable"), &TileMap::set_compatibility_mode); - ClassDB::bind_method(D_METHOD("is_compatibility_mode_enabled"), &TileMap::is_compatibility_mode_enabled); - - ClassDB::bind_method(D_METHOD("set_centered_textures", "enable"), &TileMap::set_centered_textures); - ClassDB::bind_method(D_METHOD("is_centered_textures_enabled"), &TileMap::is_centered_textures_enabled); - - ClassDB::bind_method(D_METHOD("set_collision_use_kinematic", "use_kinematic"), &TileMap::set_collision_use_kinematic); - ClassDB::bind_method(D_METHOD("get_collision_use_kinematic"), &TileMap::get_collision_use_kinematic); - - ClassDB::bind_method(D_METHOD("set_collision_use_parent", "use_parent"), &TileMap::set_collision_use_parent); - ClassDB::bind_method(D_METHOD("get_collision_use_parent"), &TileMap::get_collision_use_parent); - - ClassDB::bind_method(D_METHOD("set_collision_layer", "layer"), &TileMap::set_collision_layer); - ClassDB::bind_method(D_METHOD("get_collision_layer"), &TileMap::get_collision_layer); - - ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &TileMap::set_collision_mask); - ClassDB::bind_method(D_METHOD("get_collision_mask"), &TileMap::get_collision_mask); - - ClassDB::bind_method(D_METHOD("set_collision_layer_bit", "bit", "value"), &TileMap::set_collision_layer_bit); - ClassDB::bind_method(D_METHOD("get_collision_layer_bit", "bit"), &TileMap::get_collision_layer_bit); - - ClassDB::bind_method(D_METHOD("set_collision_mask_bit", "bit", "value"), &TileMap::set_collision_mask_bit); - ClassDB::bind_method(D_METHOD("get_collision_mask_bit", "bit"), &TileMap::get_collision_mask_bit); - - ClassDB::bind_method(D_METHOD("set_collision_friction", "value"), &TileMap::set_collision_friction); - ClassDB::bind_method(D_METHOD("get_collision_friction"), &TileMap::get_collision_friction); - - ClassDB::bind_method(D_METHOD("set_collision_bounce", "value"), &TileMap::set_collision_bounce); - ClassDB::bind_method(D_METHOD("get_collision_bounce"), &TileMap::get_collision_bounce); - - ClassDB::bind_method(D_METHOD("set_occluder_light_mask", "mask"), &TileMap::set_occluder_light_mask); - ClassDB::bind_method(D_METHOD("get_occluder_light_mask"), &TileMap::get_occluder_light_mask); - - ClassDB::bind_method(D_METHOD("set_cell", "x", "y", "tile", "flip_x", "flip_y", "transpose", "autotile_coord"), &TileMap::set_cell, DEFVAL(false), DEFVAL(false), DEFVAL(false), DEFVAL(Vector2())); - ClassDB::bind_method(D_METHOD("set_cellv", "position", "tile", "flip_x", "flip_y", "transpose"), &TileMap::set_cellv, DEFVAL(false), DEFVAL(false), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("_set_celld", "position", "data"), &TileMap::_set_celld); - ClassDB::bind_method(D_METHOD("get_cell", "x", "y"), &TileMap::get_cell); - ClassDB::bind_method(D_METHOD("get_cellv", "position"), &TileMap::get_cellv); - ClassDB::bind_method(D_METHOD("is_cell_x_flipped", "x", "y"), &TileMap::is_cell_x_flipped); - ClassDB::bind_method(D_METHOD("is_cell_y_flipped", "x", "y"), &TileMap::is_cell_y_flipped); - ClassDB::bind_method(D_METHOD("is_cell_transposed", "x", "y"), &TileMap::is_cell_transposed); - - ClassDB::bind_method(D_METHOD("get_cell_autotile_coord", "x", "y"), &TileMap::get_cell_autotile_coord); + 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", "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", "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_by_id", "id"), &TileMap::get_used_cells_by_id); + 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", "ignore_half_ofs"), &TileMap::map_to_world, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("map_to_world", "map_position"), &TileMap::map_to_world); ClassDB::bind_method(D_METHOD("world_to_map", "world_position"), &TileMap::world_to_map); - ClassDB::bind_method(D_METHOD("_clear_quadrants"), &TileMap::_clear_quadrants); - ClassDB::bind_method(D_METHOD("update_dirty_quadrants"), &TileMap::update_dirty_quadrants); + ClassDB::bind_method(D_METHOD("get_neighbor_cell", "coords", "neighbor"), &TileMap::get_neighbor_cell); - ClassDB::bind_method(D_METHOD("update_bitmask_area", "position"), &TileMap::update_bitmask_area); - ClassDB::bind_method(D_METHOD("update_bitmask_region", "start", "end"), &TileMap::update_bitmask_region, DEFVAL(Vector2()), DEFVAL(Vector2())); + 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::INT, "mode", PROPERTY_HINT_ENUM, "Square,Isometric,Custom"), "set_mode", "get_mode"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tile_set", PROPERTY_HINT_RESOURCE_TYPE, "TileSet"), "set_tileset", "get_tileset"); - - ADD_GROUP("Cell", "cell_"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "cell_size", PROPERTY_HINT_RANGE, "1,8192,1"), "set_cell_size", "get_cell_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_quadrant_size", PROPERTY_HINT_RANGE, "1,128,1"), "set_quadrant_size", "get_quadrant_size"); - ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "cell_custom_transform"), "set_custom_transform", "get_custom_transform"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_half_offset", PROPERTY_HINT_ENUM, "Offset X,Offset Y,Disabled,Offset Negative X,Offset Negative Y"), "set_half_offset", "get_half_offset"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_tile_origin", PROPERTY_HINT_ENUM, "Top Left,Center,Bottom Left"), "set_tile_origin", "get_tile_origin"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cell_y_sort"), "set_y_sort_mode", "is_y_sort_mode_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "compatibility_mode"), "set_compatibility_mode", "is_compatibility_mode_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "centered_textures"), "set_centered_textures", "is_centered_textures_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cell_clip_uv"), "set_clip_uv", "get_clip_uv"); - - ADD_GROUP("Collision", "collision_"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision_use_parent", PROPERTY_HINT_NONE, ""), "set_collision_use_parent", "get_collision_use_parent"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision_use_kinematic", PROPERTY_HINT_NONE, ""), "set_collision_use_kinematic", "get_collision_use_kinematic"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_friction", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_collision_friction", "get_collision_friction"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_bounce", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_collision_bounce", "get_collision_bounce"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_layer", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_layer", "get_collision_layer"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_mask", "get_collision_mask"); - - ADD_GROUP("Occluder", "occluder_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "occluder_light_mask", PROPERTY_HINT_LAYERS_2D_RENDER), "set_occluder_light_mask", "get_occluder_light_mask"); - - ADD_PROPERTY_DEFAULT("format", FORMAT_1); - - ADD_SIGNAL(MethodInfo("settings_changed")); + 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"); - BIND_CONSTANT(INVALID_CELL); + ADD_ARRAY("layers", "layer_"); - BIND_ENUM_CONSTANT(MODE_SQUARE); - BIND_ENUM_CONSTANT(MODE_ISOMETRIC); - BIND_ENUM_CONSTANT(MODE_CUSTOM); + ADD_PROPERTY_DEFAULT("format", FORMAT_1); - BIND_ENUM_CONSTANT(HALF_OFFSET_X); - BIND_ENUM_CONSTANT(HALF_OFFSET_Y); - BIND_ENUM_CONSTANT(HALF_OFFSET_DISABLED); - BIND_ENUM_CONSTANT(HALF_OFFSET_NEGATIVE_X); - BIND_ENUM_CONSTANT(HALF_OFFSET_NEGATIVE_Y); + ADD_SIGNAL(MethodInfo("changed")); - BIND_ENUM_CONSTANT(TILE_ORIGIN_TOP_LEFT); - BIND_ENUM_CONSTANT(TILE_ORIGIN_CENTER); - BIND_ENUM_CONSTANT(TILE_ORIGIN_BOTTOM_LEFT); + BIND_ENUM_CONSTANT(VISIBILITY_MODE_DEFAULT); + BIND_ENUM_CONSTANT(VISIBILITY_MODE_FORCE_HIDE); + BIND_ENUM_CONSTANT(VISIBILITY_MODE_FORCE_SHOW); } -void TileMap::_changed_callback(Object *p_changed, const char *p_prop) { - if (tile_set.is_valid() && tile_set.ptr() == p_changed) { - emit_signal("settings_changed"); - } +void TileMap::_tile_set_changed() { + emit_signal(SNAME("changed")); + _clear_internals(); + _recreate_internals(); } TileMap::TileMap() { - - rect_cache_dirty = true; - used_size_cache_dirty = true; - pending_update = false; - quadrant_order_dirty = false; - quadrant_size = 16; - cell_size = Size2(64, 64); - custom_transform = Transform2D(64, 0, 0, 64, 0, 0); - collision_layer = 1; - collision_mask = 1; - friction = 1; - bounce = 0; - mode = MODE_SQUARE; - half_offset = HALF_OFFSET_DISABLED; - use_parent = false; - collision_parent = nullptr; - use_kinematic = false; - navigation = nullptr; - y_sort_mode = false; - compatibility_mode = false; - centered_textures = false; - occluder_light_mask = 1; - clip_uv = false; - format = FORMAT_1; // Assume lowest possible format if none is present - - fp_adjust = 0.00001; - tile_origin = TILE_ORIGIN_TOP_LEFT; set_notify_transform(true); set_notify_local_transform(false); + + layers.resize(1); } TileMap::~TileMap() { - - if (tile_set.is_valid()) - tile_set->remove_change_receptor(this); - - clear(); + if (tile_set.is_valid()) { + tile_set->disconnect("changed", callable_mp(this, &TileMap::_tile_set_changed)); + } + _clear_internals(); } diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index d9490aae13..3ac50fc7cc 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,202 +31,254 @@ #ifndef TILE_MAP_H #define TILE_MAP_H -#include "core/self_list.h" -#include "core/vset.h" -#include "scene/2d/navigation_2d.h" #include "scene/2d/node_2d.h" +#include "scene/gui/control.h" #include "scene/resources/tile_set.h" -class CollisionObject2D; +class TileSetAtlasSource; -class TileMap : public Node2D { - - GDCLASS(TileMap, Node2D); - -public: - enum Mode { - MODE_SQUARE, - MODE_ISOMETRIC, - MODE_CUSTOM - }; - - enum HalfOffset { - HALF_OFFSET_X, - HALF_OFFSET_Y, - HALF_OFFSET_DISABLED, - HALF_OFFSET_NEGATIVE_X, - HALF_OFFSET_NEGATIVE_Y, - }; - - enum TileOrigin { - TILE_ORIGIN_TOP_LEFT, - TILE_ORIGIN_CENTER, - TILE_ORIGIN_BOTTOM_LEFT +union TileMapCell { + struct { + int32_t source_id : 16; + int16_t coord_x : 16; + int16_t coord_y : 16; + int32_t alternative_tile : 16; }; -private: - enum DataFormat { - FORMAT_1 = 0, - FORMAT_2 - }; - - Ref<TileSet> tile_set; - Size2i cell_size; - int quadrant_size; - Mode mode; - Transform2D custom_transform; - HalfOffset half_offset; - bool use_parent; - CollisionObject2D *collision_parent; - bool use_kinematic; - Navigation2D *navigation; - - union PosKey { - - struct { - int16_t x; - int16_t y; - }; - uint32_t key; - - //using a more precise comparison so the regions can be sorted later - bool operator<(const PosKey &p_k) const { return (y == p_k.y) ? x < p_k.x : y < p_k.y; } - - bool operator==(const PosKey &p_k) const { return (y == p_k.y && x == p_k.x); } - - PosKey to_quadrant(const int &p_quadrant_size) const { - // rounding down, instead of simply rounding towards zero (truncating) - return PosKey( - x > 0 ? x / p_quadrant_size : (x - (p_quadrant_size - 1)) / p_quadrant_size, - y > 0 ? y / p_quadrant_size : (y - (p_quadrant_size - 1)) / p_quadrant_size); + uint64_t _u64t; + TileMapCell(int p_source_id = -1, Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE) { + source_id = p_source_id; + set_atlas_coords(p_atlas_coords); + alternative_tile = p_alternative_tile; + } + + Vector2i get_atlas_coords() const { + return Vector2i(coord_x, coord_y); + } + + void set_atlas_coords(const Vector2i &r_coords) { + coord_x = r_coords.x; + coord_y = r_coords.y; + } + + bool operator<(const TileMapCell &p_other) const { + if (source_id == p_other.source_id) { + if (coord_x == p_other.coord_x) { + if (coord_y == p_other.coord_y) { + return alternative_tile < p_other.alternative_tile; + } else { + return coord_y < p_other.coord_y; + } + } else { + return coord_x < p_other.coord_x; + } + } else { + return source_id < p_other.source_id; } + } - PosKey(int16_t p_x, int16_t p_y) { - x = p_x; - y = p_y; - } - PosKey() { - x = 0; - y = 0; + bool operator!=(const TileMapCell &p_other) const { + return !(source_id == p_other.source_id && coord_x == p_other.coord_x && coord_y == p_other.coord_y && alternative_tile == p_other.alternative_tile); + } +}; + +struct TileMapQuadrant { + struct CoordsWorldComparator { + _ALWAYS_INLINE_ bool operator()(const Vector2i &p_a, const Vector2i &p_b) const { + // We sort the cells by their world coords, as it is needed by rendering. + if (p_a.y == p_b.y) { + return p_a.x > p_b.x; + } else { + return p_a.y < p_b.y; + } } }; - union Cell { + // Dirty list element + SelfList<TileMapQuadrant> dirty_list_element; + + // Quadrant layer and coords. + int layer = -1; + Vector2i coords; + + // TileMapCells + Set<Vector2i> cells; + // We need those two maps to sort by world position for rendering + // This is kind of workaround, it would be better to sort the cells directly in the "cells" set instead. + Map<Vector2i, Vector2i> map_to_world; + Map<Vector2i, Vector2i, CoordsWorldComparator> world_to_map; + + // Debug. + RID debug_canvas_item; + + // Rendering. + List<RID> canvas_items; + List<RID> occluders; + + // Physics. + List<RID> bodies; + + // Navigation. + Map<Vector2i, Vector<RID>> navigation_regions; + + // Scenes. + 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; + occluders = q.occluders; + bodies = q.bodies; + navigation_regions = q.navigation_regions; + } + + 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; + occluders = q.occluders; + bodies = q.bodies; + navigation_regions = q.navigation_regions; + } + + TileMapQuadrant() : + dirty_list_element(this) { + } +}; - struct { - int32_t id : 24; - bool flip_h : 1; - bool flip_v : 1; - bool transpose : 1; - int16_t autotile_coord_x : 16; - int16_t autotile_coord_y : 16; - }; +class TileMapPattern : public Object { + GDCLASS(TileMapPattern, Object); - uint64_t _u64t; - Cell() { _u64t = 0; } - }; + Vector2i size; + Map<Vector2i, TileMapCell> pattern; - Map<PosKey, Cell> tile_map; - List<PosKey> dirty_bitmask; +protected: + static void _bind_methods(); - struct Quadrant { +public: + void set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile = 0); + bool has_cell(const Vector2i &p_coords) const; + void remove_cell(const Vector2i &p_coords, bool p_update_size = true); + 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; - Vector2 pos; - List<RID> canvas_items; - RID body; - uint32_t shape_owner_id; + TypedArray<Vector2i> get_used_cells() const; - SelfList<Quadrant> dirty_list; + Vector2i get_size() const; + void set_size(const Vector2i &p_size); + bool is_empty() const; - struct NavPoly { - RID region; - Transform2D xform; - }; + void clear(); +}; - struct Occluder { - RID id; - Transform2D xform; - }; +class TileMap : public Node2D { + GDCLASS(TileMap, Node2D); - Map<PosKey, NavPoly> navpoly_ids; - Map<PosKey, Occluder> occluder_instances; +public: + enum VisibilityMode { + VISIBILITY_MODE_DEFAULT, + VISIBILITY_MODE_FORCE_SHOW, + VISIBILITY_MODE_FORCE_HIDE, + }; - VSet<PosKey> cells; +private: + friend class TileSetPlugin; - void operator=(const Quadrant &q) { - pos = q.pos; - canvas_items = q.canvas_items; - body = q.body; - shape_owner_id = q.shape_owner_id; - cells = q.cells; - navpoly_ids = q.navpoly_ids; - occluder_instances = q.occluder_instances; - } - Quadrant(const Quadrant &q) : - dirty_list(this) { - pos = q.pos; - canvas_items = q.canvas_items; - body = q.body; - shape_owner_id = q.shape_owner_id; - cells = q.cells; - occluder_instances = q.occluder_instances; - navpoly_ids = q.navpoly_ids; - } - Quadrant() : - dirty_list(this) {} + // A compatibility enum to specify how is the data if formatted. + enum DataFormat { + FORMAT_1 = 0, + FORMAT_2, + FORMAT_3 }; + mutable DataFormat format = FORMAT_1; // Assume lowest possible format if none is present; - Map<PosKey, Quadrant> quadrant_map; + static constexpr float FP_ADJUST = 0.00001; - SelfList<Quadrant>::List dirty_quadrant_list; + // Properties. + Ref<TileSet> tile_set; + int quadrant_size = 16; + VisibilityMode collision_visibility_mode = VISIBILITY_MODE_DEFAULT; + VisibilityMode navigation_visibility_mode = VISIBILITY_MODE_DEFAULT; - bool pending_update; + // Updates. + bool pending_update = false; + // Rect. Rect2 rect_cache; - bool rect_cache_dirty; - Rect2 used_size_cache; - bool used_size_cache_dirty; - bool quadrant_order_dirty; - bool y_sort_mode; - bool compatibility_mode; - bool centered_textures; - bool clip_uv; - float fp_adjust; - float friction; - float bounce; - uint32_t collision_layer; - uint32_t collision_mask; - mutable DataFormat format; - - TileOrigin tile_origin; - - int occluder_light_mask; - - void _fix_cell_transform(Transform2D &xform, const Cell &p_cell, const Vector2 &p_offset, const Size2 &p_sc); - - void _add_shape(int &shape_idx, const Quadrant &p_q, const Ref<Shape2D> &p_shape, const TileSet::ShapeData &p_shape_data, const Transform2D &p_xform, const Vector2 &p_metadata); - - Map<PosKey, Quadrant>::Element *_create_quadrant(const PosKey &p_qk); - void _erase_quadrant(Map<PosKey, Quadrant>::Element *Q); - void _make_quadrant_dirty(Map<PosKey, Quadrant>::Element *Q, bool update = true); - void _recreate_quadrants(); - void _clear_quadrants(); - void _update_quadrant_space(const RID &p_space); - void _update_quadrant_transform(); - void _recompute_rect_cache(); + bool rect_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); - void _update_all_items_material_state(); - _FORCE_INLINE_ void _update_item_material_state(const RID &p_canvas_item); + void _make_quadrant_dirty(Map<Vector2i, TileMapQuadrant>::Element *Q); + void _make_all_quadrants_dirty(); + void _queue_update_dirty_quadrants(); - _FORCE_INLINE_ int _get_quadrant_size() const; + void _update_dirty_quadrants(); - void _set_tile_data(const Vector<int> &p_data); - Vector<int> _get_tile_data() const; + void _recreate_internals(); - void _set_old_cell_size(int p_size) { set_cell_size(Size2(p_size, p_size)); } - int _get_old_cell_size() const { return cell_size.x; } + void _erase_quadrant(Map<Vector2i, TileMapQuadrant>::Element *Q); + void _clear_layer_internals(int p_layer); + void _clear_internals(); + + // Rect caching. + void _recompute_rect_cache(); - _FORCE_INLINE_ Vector2 _map_to_world(int p_x, int p_y, bool p_ignore_ofs = false) const; + // 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(int p_layer, const Vector<int> &p_data); + Vector<int> _get_tile_data(int p_layer) const; + + void _tile_set_changed(); protected: bool _set(const StringName &p_name, const Variant &p_value); @@ -236,125 +288,99 @@ protected: void _notification(int p_what); static void _bind_methods(); - virtual void _validate_property(PropertyInfo &property) const; - virtual void _changed_callback(Object *p_changed, const char *p_prop); - public: + static Vector2i transform_coords_layout(Vector2i p_coords, TileSet::TileOffsetAxis p_offset_axis, TileSet::TileLayout p_from_layout, TileSet::TileLayout p_to_layout); + enum { INVALID_CELL = -1 }; #ifdef TOOLS_ENABLED - virtual Rect2 _edit_get_rect() const; + virtual Rect2 _edit_get_rect() const override; #endif void set_tileset(const Ref<TileSet> &p_tileset); Ref<TileSet> get_tileset() const; - void set_cell_size(Size2 p_size); - Size2 get_cell_size() const; - void set_quadrant_size(int p_size); int get_quadrant_size() const; - void set_cell(int p_x, int p_y, int p_tile, bool p_flip_x = false, bool p_flip_y = false, bool p_transpose = false, Vector2 p_autotile_coord = Vector2()); - int get_cell(int p_x, int p_y) const; - bool is_cell_x_flipped(int p_x, int p_y) const; - bool is_cell_y_flipped(int p_x, int p_y) const; - bool is_cell_transposed(int p_x, int p_y) const; - void set_cell_autotile_coord(int p_x, int p_y, const Vector2 &p_coord); - Vector2 get_cell_autotile_coord(int p_x, int p_y) const; - - void _set_celld(const Vector2 &p_pos, const Dictionary &p_data); - void set_cellv(const Vector2 &p_pos, int p_tile, bool p_flip_x = false, bool p_flip_y = false, bool p_transpose = false); - int get_cellv(const Vector2 &p_pos) const; - - void make_bitmask_area_dirty(const Vector2 &p_pos); - void update_bitmask_area(const Vector2 &p_pos); - void update_bitmask_region(const Vector2 &p_start = Vector2(), const Vector2 &p_end = Vector2()); - void update_cell_bitmask(int p_x, int p_y); - void update_dirty_bitmask(); - - void update_dirty_quadrants(); - - void set_collision_layer(uint32_t p_layer); - uint32_t get_collision_layer() const; - - 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_mask_bit(int p_bit, bool p_value); - bool get_collision_mask_bit(int p_bit) const; - - void set_collision_use_kinematic(bool p_use_kinematic); - bool get_collision_use_kinematic() const; - - void set_collision_use_parent(bool p_use_parent); - bool get_collision_use_parent() const; - - void set_collision_friction(float p_friction); - float get_collision_friction() const; - - void set_collision_bounce(float p_bounce); - float get_collision_bounce() const; - - void set_mode(Mode p_mode); - Mode get_mode() const; - - void set_half_offset(HalfOffset p_half_offset); - HalfOffset get_half_offset() const; - - void set_tile_origin(TileOrigin p_tile_origin); - TileOrigin get_tile_origin() const; - - void set_custom_transform(const Transform2D &p_xform); - Transform2D get_custom_transform() const; - - Transform2D get_cell_transform() const; - Vector2 get_cell_draw_offset() const; - - Vector2 map_to_world(const Vector2 &p_pos, bool p_ignore_ofs = false) const; - Vector2 world_to_map(const Vector2 &p_pos) const; - - void set_y_sort_mode(bool p_enable); - bool is_y_sort_mode_enabled() const; - - void set_compatibility_mode(bool p_enable); - bool is_compatibility_mode_enabled() const; - - void set_centered_textures(bool p_enable); - bool is_centered_textures_enabled() const; - - Array get_used_cells() const; - Array get_used_cells_by_id(int p_id) 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(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(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(int p_layer, Vector2i p_position, const TileMapPattern *p_pattern); + + // Not exposed to users + TileMapCell get_cell(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const; + Map<Vector2i, TileMapQuadrant> *get_quadrant_map(int p_layer); + int get_effective_quadrant_size(int p_layer) const; + //--- + + virtual void set_y_sort_enabled(bool p_enable) override; + + Vector2 map_to_world(const Vector2i &p_pos) const; + Vector2i world_to_map(const Vector2 &p_pos) const; + + 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(int p_layer) const; Rect2 get_used_rect(); // Not const because of cache - void set_occluder_light_mask(int p_mask); - int get_occluder_light_mask() const; - - virtual void set_light_mask(int p_light_mask); - - virtual void set_material(const Ref<Material> &p_material); + // Override some methods of the CanvasItem class to pass the changes to the quadrants CanvasItems + virtual void set_light_mask(int p_light_mask) override; + virtual void set_material(const Ref<Material> &p_material) override; + virtual void set_use_parent_material(bool p_use_parent_material) override; + virtual void set_texture_filter(CanvasItem::TextureFilter p_texture_filter) override; + virtual void set_texture_repeat(CanvasItem::TextureRepeat p_texture_repeat) override; - virtual void set_use_parent_material(bool p_use_parent_material); + // Fixing a nclearing methods. + void fix_invalid_tiles(); - void set_clip_uv(bool p_enable); - bool get_clip_uv() const; + void clear_layer(int p_layer); + void clear(); - String get_configuration_warning() const; + // Helpers + TypedArray<Vector2i> get_surrounding_tiles(Vector2i coords); + void draw_cells_outline(Control *p_control, Set<Vector2i> p_cells, Color p_color, Transform2D p_transform = Transform2D()); - void fix_invalid_tiles(); - void clear(); + // Configuration warnings. + TypedArray<String> get_configuration_warnings() const override; TileMap(); ~TileMap(); }; -VARIANT_ENUM_CAST(TileMap::Mode); -VARIANT_ENUM_CAST(TileMap::HalfOffset); -VARIANT_ENUM_CAST(TileMap::TileOrigin); +VARIANT_ENUM_CAST(TileMap::VisibilityMode); #endif // TILE_MAP_H diff --git a/scene/2d/touch_screen_button.cpp b/scene/2d/touch_screen_button.cpp index 2cb979a0e0..8bd7b696f2 100644 --- a/scene/2d/touch_screen_button.cpp +++ b/scene/2d/touch_screen_button.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,195 +30,188 @@ #include "touch_screen_button.h" -#include "core/input/input_filter.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) { +void TouchScreenButton::set_texture(const Ref<Texture2D> &p_texture) { texture = p_texture; update(); } Ref<Texture2D> TouchScreenButton::get_texture() const { - return texture; } void TouchScreenButton::set_texture_pressed(const Ref<Texture2D> &p_texture_pressed) { - texture_pressed = p_texture_pressed; update(); } Ref<Texture2D> TouchScreenButton::get_texture_pressed() const { - return texture_pressed; } void TouchScreenButton::set_bitmask(const Ref<BitMap> &p_bitmask) { - bitmask = p_bitmask; } Ref<BitMap> TouchScreenButton::get_bitmask() const { - return bitmask; } void TouchScreenButton::set_shape(const Ref<Shape2D> &p_shape) { - - if (shape.is_valid()) + if (shape.is_valid()) { shape->disconnect("changed", callable_mp((CanvasItem *)this, &CanvasItem::update)); + } shape = p_shape; - if (shape.is_valid()) + if (shape.is_valid()) { shape->connect("changed", callable_mp((CanvasItem *)this, &CanvasItem::update)); + } update(); } Ref<Shape2D> TouchScreenButton::get_shape() const { - return shape; } void TouchScreenButton::set_shape_centered(bool p_shape_centered) { - shape_centered = p_shape_centered; update(); } bool TouchScreenButton::is_shape_visible() const { - return shape_visible; } void TouchScreenButton::set_shape_visible(bool p_shape_visible) { - shape_visible = p_shape_visible; update(); } bool TouchScreenButton::is_shape_centered() const { - return shape_centered; } void TouchScreenButton::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_DRAW: { - - if (!is_inside_tree()) + if (!is_inside_tree()) { return; - if (!Engine::get_singleton()->is_editor_hint() && !!DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id())) && visibility == VISIBILITY_TOUCHSCREEN_ONLY) + } + if (!Engine::get_singleton()->is_editor_hint() && !!DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id())) && visibility == VISIBILITY_TOUCHSCREEN_ONLY) { return; + } if (finger_pressed != -1) { - - if (texture_pressed.is_valid()) + if (texture_pressed.is_valid()) { draw_texture(texture_pressed, Point2()); - else if (texture.is_valid()) + } else if (texture.is_valid()) { draw_texture(texture, Point2()); + } } else { - if (texture.is_valid()) + if (texture.is_valid()) { draw_texture(texture, Point2()); + } } - if (!shape_visible) + if (!shape_visible) { return; - if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) + } + if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) { return; + } if (shape.is_valid()) { Color draw_col = get_tree()->get_debug_collisions_color(); - Vector2 size = texture.is_null() ? shape->get_rect().size : texture->get_size(); - Vector2 pos = shape_centered ? size * 0.5f : Vector2(); + Vector2 pos; + if (shape_centered && texture.is_valid()) { + pos = texture->get_size() * 0.5; + } + draw_set_transform_matrix(get_canvas_transform().translated(pos)); shape->draw(get_canvas_item(), draw_col); } } break; case NOTIFICATION_ENTER_TREE: { - - if (!Engine::get_singleton()->is_editor_hint() && !!DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id())) && visibility == VISIBILITY_TOUCHSCREEN_ONLY) + if (!Engine::get_singleton()->is_editor_hint() && !!DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id())) && visibility == VISIBILITY_TOUCHSCREEN_ONLY) { return; + } update(); - if (!Engine::get_singleton()->is_editor_hint()) + if (!Engine::get_singleton()->is_editor_hint()) { set_process_input(is_visible_in_tree()); + } } break; case NOTIFICATION_EXIT_TREE: { - if (is_pressed()) + if (is_pressed()) { _release(true); + } } break; case NOTIFICATION_VISIBILITY_CHANGED: { - if (Engine::get_singleton()->is_editor_hint()) + if (Engine::get_singleton()->is_editor_hint()) { break; + } if (is_visible_in_tree()) { set_process_input(true); } else { set_process_input(false); - if (is_pressed()) + if (is_pressed()) { _release(); + } } } break; case NOTIFICATION_PAUSED: { - if (is_pressed()) + if (is_pressed()) { _release(); + } } break; } } bool TouchScreenButton::is_pressed() const { - return finger_pressed != -1; } void TouchScreenButton::set_action(const String &p_action) { - action = p_action; } 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()) + if (!get_tree()) { return; + } - if (p_event->get_device() != 0) + if (p_event->get_device() != 0) { return; + } ERR_FAIL_COND(!is_visible_in_tree()); const InputEventScreenTouch *st = Object::cast_to<InputEventScreenTouch>(*p_event); if (passby_press) { - const InputEventScreenDrag *sd = Object::cast_to<InputEventScreenDrag>(*p_event); if (st && !st->is_pressed() && finger_pressed == st->get_index()) { - _release(); } if ((st && st->is_pressed()) || sd) { - int index = st ? st->get_index() : sd->get_index(); Point2 coord = st ? st->get_position() : sd->get_position(); if (finger_pressed == -1 || index == finger_pressed) { - if (_is_point_inside(coord)) { if (finger_pressed == -1) { _press(index); @@ -232,14 +225,12 @@ void TouchScreenButton::_input(const Ref<InputEvent> &p_event) { } } else { - if (st) { - if (st->is_pressed()) { - const bool can_press = finger_pressed == -1; - if (!can_press) + if (!can_press) { return; //already fingering + } if (_is_point_inside(st->get_position())) { _press(st->get_index()); @@ -260,75 +251,75 @@ bool TouchScreenButton::_is_point_inside(const Point2 &p_point) { bool check_rect = true; if (shape.is_valid()) { - check_rect = false; - Transform2D xform = shape_centered ? Transform2D().translated(shape->get_rect().size * 0.5f) : Transform2D(); - touched = shape->collide(xform, unit_rect, Transform2D(0, coord + Vector2(0.5, 0.5))); + + Vector2 pos; + if (shape_centered && texture.is_valid()) { + pos = texture->get_size() * 0.5; + } + + touched = shape->collide(Transform2D().translated(pos), unit_rect, Transform2D(0, coord + Vector2(0.5, 0.5))); } if (bitmask.is_valid()) { - check_rect = false; if (!touched && Rect2(Point2(), bitmask->get_size()).has_point(coord)) { - - if (bitmask->get_bit(coord)) + if (bitmask->get_bit(coord)) { touched = true; + } } } if (!touched && check_rect) { - if (texture.is_valid()) + if (texture.is_valid()) { touched = Rect2(Size2(), texture->get_size()).has_point(coord); + } } return touched; } void TouchScreenButton::_press(int p_finger_pressed) { - finger_pressed = p_finger_pressed; if (action != StringName()) { - - InputFilter::get_singleton()->action_press(action); + Input::get_singleton()->action_press(action); Ref<InputEventAction> iea; - iea.instance(); + 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(); } void TouchScreenButton::_release(bool p_exiting_tree) { - finger_pressed = -1; if (action != StringName()) { - - InputFilter::get_singleton()->action_release(action); + Input::get_singleton()->action_release(action); if (!p_exiting_tree) { - Ref<InputEventAction> iea; - iea.instance(); + 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(); } } #ifdef TOOLS_ENABLED Rect2 TouchScreenButton::_edit_get_rect() const { - if (texture.is_null()) + if (texture.is_null()) { return CanvasItem::_edit_get_rect(); + } return Rect2(Size2(), texture->get_size()); } @@ -339,8 +330,9 @@ bool TouchScreenButton::_edit_use_rect() const { #endif Rect2 TouchScreenButton::get_anchorable_rect() const { - if (texture.is_null()) + if (texture.is_null()) { return CanvasItem::get_anchorable_rect(); + } return Rect2(Size2(), texture->get_size()); } @@ -351,22 +343,18 @@ void TouchScreenButton::set_visibility_mode(VisibilityMode p_mode) { } TouchScreenButton::VisibilityMode TouchScreenButton::get_visibility_mode() const { - return visibility; } void TouchScreenButton::set_passby_press(bool p_enable) { - passby_press = p_enable; } bool TouchScreenButton::is_passby_press_enabled() const { - return passby_press; } void TouchScreenButton::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_texture", "texture"), &TouchScreenButton::set_texture); ClassDB::bind_method(D_METHOD("get_texture"), &TouchScreenButton::get_texture); @@ -396,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"); @@ -416,12 +402,6 @@ void TouchScreenButton::_bind_methods() { } TouchScreenButton::TouchScreenButton() { - - finger_pressed = -1; - passby_press = false; - visibility = VISIBILITY_ALWAYS; - shape_centered = true; - shape_visible = true; unit_rect = Ref<RectangleShape2D>(memnew(RectangleShape2D)); - unit_rect->set_extents(Vector2(0.5, 0.5)); + unit_rect->set_size(Vector2(1, 1)); } diff --git a/scene/2d/touch_screen_button.h b/scene/2d/touch_screen_button.h index 42e93f7048..1c515149d4 100644 --- a/scene/2d/touch_screen_button.h +++ b/scene/2d/touch_screen_button.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -37,7 +37,6 @@ #include "scene/resources/texture.h" class TouchScreenButton : public Node2D { - GDCLASS(TouchScreenButton, Node2D); public: @@ -51,18 +50,18 @@ private: Ref<Texture2D> texture_pressed; Ref<BitMap> bitmask; Ref<Shape2D> shape; - bool shape_centered; - bool shape_visible; + bool shape_centered = true; + bool shape_visible = true; Ref<RectangleShape2D> unit_rect; StringName action; - bool passby_press; - int finger_pressed; + bool passby_press = false; + int finger_pressed = -1; - VisibilityMode visibility; + 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); @@ -75,8 +74,8 @@ protected: public: #ifdef TOOLS_ENABLED - virtual Rect2 _edit_get_rect() const; - virtual bool _edit_use_rect() const; + virtual Rect2 _edit_get_rect() const override; + virtual bool _edit_use_rect() const override; #endif void set_texture(const Ref<Texture2D> &p_texture); @@ -108,7 +107,7 @@ public: bool is_pressed() const; - virtual Rect2 get_anchorable_rect() const; + virtual Rect2 get_anchorable_rect() const override; TouchScreenButton(); }; diff --git a/scene/2d/visibility_notifier_2d.cpp b/scene/2d/visibility_notifier_2d.cpp deleted file mode 100644 index c374dd5faa..0000000000 --- a/scene/2d/visibility_notifier_2d.cpp +++ /dev/null @@ -1,373 +0,0 @@ -/*************************************************************************/ -/* visibility_notifier_2d.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 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 "visibility_notifier_2d.h" - -#include "core/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 -Rect2 VisibilityNotifier2D::_edit_get_rect() const { - return rect; -} - -bool VisibilityNotifier2D::_edit_use_rect() const { - return true; -} -#endif - -void VisibilityNotifier2D::_enter_viewport(Viewport *p_viewport) { - - ERR_FAIL_COND(viewports.has(p_viewport)); - viewports.insert(p_viewport); - - if (is_inside_tree() && Engine::get_singleton()->is_editor_hint()) - return; - - if (viewports.size() == 1) { - emit_signal(SceneStringNames::get_singleton()->screen_entered); - - _screen_enter(); - } - emit_signal(SceneStringNames::get_singleton()->viewport_entered, p_viewport); -} - -void VisibilityNotifier2D::_exit_viewport(Viewport *p_viewport) { - - ERR_FAIL_COND(!viewports.has(p_viewport)); - viewports.erase(p_viewport); - - if (is_inside_tree() && Engine::get_singleton()->is_editor_hint()) - return; - - emit_signal(SceneStringNames::get_singleton()->viewport_exited, p_viewport); - if (viewports.size() == 0) { - emit_signal(SceneStringNames::get_singleton()->screen_exited); - - _screen_exit(); - } -} - -void VisibilityNotifier2D::set_rect(const Rect2 &p_rect) { - - rect = p_rect; - if (is_inside_tree()) { - get_world_2d()->_update_notifier(this, get_global_transform().xform(rect)); - if (Engine::get_singleton()->is_editor_hint()) { - update(); - item_rect_changed(); - } - } - - _change_notify("rect"); -} - -Rect2 VisibilityNotifier2D::get_rect() const { - - return rect; -} - -void VisibilityNotifier2D::_notification(int p_what) { - - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - - //get_world_2d()-> - get_world_2d()->_register_notifier(this, get_global_transform().xform(rect)); - } break; - case NOTIFICATION_TRANSFORM_CHANGED: { - - //get_world_2d()-> - get_world_2d()->_update_notifier(this, get_global_transform().xform(rect)); - } break; - case NOTIFICATION_DRAW: { - - if (Engine::get_singleton()->is_editor_hint()) { - - draw_rect(rect, Color(1, 0.5, 1, 0.2)); - } - } break; - case NOTIFICATION_EXIT_TREE: { - - get_world_2d()->_remove_notifier(this); - } break; - } -} - -bool VisibilityNotifier2D::is_on_screen() const { - - return viewports.size() > 0; -} - -void VisibilityNotifier2D::_bind_methods() { - - ClassDB::bind_method(D_METHOD("set_rect", "rect"), &VisibilityNotifier2D::set_rect); - ClassDB::bind_method(D_METHOD("get_rect"), &VisibilityNotifier2D::get_rect); - ClassDB::bind_method(D_METHOD("is_on_screen"), &VisibilityNotifier2D::is_on_screen); - - ADD_PROPERTY(PropertyInfo(Variant::RECT2, "rect"), "set_rect", "get_rect"); - - ADD_SIGNAL(MethodInfo("viewport_entered", PropertyInfo(Variant::OBJECT, "viewport", PROPERTY_HINT_RESOURCE_TYPE, "Viewport"))); - ADD_SIGNAL(MethodInfo("viewport_exited", PropertyInfo(Variant::OBJECT, "viewport", PROPERTY_HINT_RESOURCE_TYPE, "Viewport"))); - ADD_SIGNAL(MethodInfo("screen_entered")); - ADD_SIGNAL(MethodInfo("screen_exited")); -} - -VisibilityNotifier2D::VisibilityNotifier2D() { - - rect = Rect2(-10, -10, 20, 20); - set_notify_transform(true); -} - -////////////////////////////////////// - -void VisibilityEnabler2D::_screen_enter() { - - for (Map<Node *, Variant>::Element *E = nodes.front(); E; E = E->next()) { - - _change_node_state(E->key(), true); - } - - if (enabler[ENABLER_PARENT_PHYSICS_PROCESS] && get_parent()) - get_parent()->set_physics_process(true); - if (enabler[ENABLER_PARENT_PROCESS] && get_parent()) - get_parent()->set_process(true); - - visible = true; -} - -void VisibilityEnabler2D::_screen_exit() { - - for (Map<Node *, Variant>::Element *E = nodes.front(); E; E = E->next()) { - - _change_node_state(E->key(), false); - } - - if (enabler[ENABLER_PARENT_PHYSICS_PROCESS] && get_parent()) - get_parent()->set_physics_process(false); - if (enabler[ENABLER_PARENT_PROCESS] && get_parent()) - get_parent()->set_process(false); - - visible = false; -} - -void VisibilityEnabler2D::_find_nodes(Node *p_node) { - - bool add = false; - Variant meta; - - { - RigidBody2D *rb2d = Object::cast_to<RigidBody2D>(p_node); - if (rb2d && ((rb2d->get_mode() == RigidBody2D::MODE_CHARACTER || rb2d->get_mode() == RigidBody2D::MODE_RIGID))) { - - add = true; - meta = rb2d->get_mode(); - } - } - - { - AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(p_node); - if (ap) { - add = true; - } - } - - { - AnimatedSprite2D *as = Object::cast_to<AnimatedSprite2D>(p_node); - if (as) { - add = true; - } - } - - { - GPUParticles2D *ps = Object::cast_to<GPUParticles2D>(p_node); - if (ps) { - add = true; - } - } - - if (add) { - - p_node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &VisibilityEnabler2D::_node_removed), varray(p_node), CONNECT_ONESHOT); - nodes[p_node] = meta; - _change_node_state(p_node, false); - } - - for (int i = 0; i < p_node->get_child_count(); i++) { - Node *c = p_node->get_child(i); - if (c->get_filename() != String()) - continue; //skip, instance - - _find_nodes(c); - } -} - -void VisibilityEnabler2D::_notification(int p_what) { - - if (p_what == NOTIFICATION_ENTER_TREE) { - - if (Engine::get_singleton()->is_editor_hint()) - return; - - Node *from = this; - //find where current scene starts - while (from->get_parent() && from->get_filename() == String()) - from = from->get_parent(); - - _find_nodes(from); - - if (enabler[ENABLER_PARENT_PHYSICS_PROCESS] && get_parent()) - get_parent()->set_physics_process(false); - if (enabler[ENABLER_PARENT_PROCESS] && get_parent()) - get_parent()->set_process(false); - } - - if (p_what == NOTIFICATION_EXIT_TREE) { - - if (Engine::get_singleton()->is_editor_hint()) - return; - - for (Map<Node *, Variant>::Element *E = nodes.front(); E; E = E->next()) { - - if (!visible) - _change_node_state(E->key(), true); - E->key()->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &VisibilityEnabler2D::_node_removed)); - } - - nodes.clear(); - } -} - -void VisibilityEnabler2D::_change_node_state(Node *p_node, bool p_enabled) { - - ERR_FAIL_COND(!nodes.has(p_node)); - - if (enabler[ENABLER_FREEZE_BODIES]) { - RigidBody2D *rb = Object::cast_to<RigidBody2D>(p_node); - if (rb) { - - rb->set_sleeping(!p_enabled); - } - } - - if (enabler[ENABLER_PAUSE_ANIMATIONS]) { - AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(p_node); - - if (ap) { - - ap->set_active(p_enabled); - } - } - - if (enabler[ENABLER_PAUSE_ANIMATED_SPRITES]) { - AnimatedSprite2D *as = Object::cast_to<AnimatedSprite2D>(p_node); - - if (as) { - - if (p_enabled) - as->play(); - else - as->stop(); - } - } - - if (enabler[ENABLER_PAUSE_PARTICLES]) { - GPUParticles2D *ps = Object::cast_to<GPUParticles2D>(p_node); - - if (ps) { - - ps->set_emitting(p_enabled); - } - } -} - -void VisibilityEnabler2D::_node_removed(Node *p_node) { - - if (!visible) - _change_node_state(p_node, true); - nodes.erase(p_node); -} - -String VisibilityEnabler2D::get_configuration_warning() const { -#ifdef TOOLS_ENABLED - if (is_inside_tree() && get_parent() && (get_parent()->get_filename() == String() && get_parent() != get_tree()->get_edited_scene_root())) { - return TTR("VisibilityEnabler2D works best when used with the edited scene root directly as parent."); - } -#endif - return String(); -} - -void VisibilityEnabler2D::_bind_methods() { - - ClassDB::bind_method(D_METHOD("set_enabler", "enabler", "enabled"), &VisibilityEnabler2D::set_enabler); - ClassDB::bind_method(D_METHOD("is_enabler_enabled", "enabler"), &VisibilityEnabler2D::is_enabler_enabled); - ClassDB::bind_method(D_METHOD("_node_removed"), &VisibilityEnabler2D::_node_removed); - - ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "pause_animations"), "set_enabler", "is_enabler_enabled", ENABLER_PAUSE_ANIMATIONS); - ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "freeze_bodies"), "set_enabler", "is_enabler_enabled", ENABLER_FREEZE_BODIES); - ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "pause_particles"), "set_enabler", "is_enabler_enabled", ENABLER_PAUSE_PARTICLES); - ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "pause_animated_sprites"), "set_enabler", "is_enabler_enabled", ENABLER_PAUSE_ANIMATED_SPRITES); - ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "process_parent"), "set_enabler", "is_enabler_enabled", ENABLER_PARENT_PROCESS); - ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "physics_process_parent"), "set_enabler", "is_enabler_enabled", ENABLER_PARENT_PHYSICS_PROCESS); - - BIND_ENUM_CONSTANT(ENABLER_PAUSE_ANIMATIONS); - BIND_ENUM_CONSTANT(ENABLER_FREEZE_BODIES); - BIND_ENUM_CONSTANT(ENABLER_PAUSE_PARTICLES); - BIND_ENUM_CONSTANT(ENABLER_PARENT_PROCESS); - BIND_ENUM_CONSTANT(ENABLER_PARENT_PHYSICS_PROCESS); - BIND_ENUM_CONSTANT(ENABLER_PAUSE_ANIMATED_SPRITES); - BIND_ENUM_CONSTANT(ENABLER_MAX); -} - -void VisibilityEnabler2D::set_enabler(Enabler p_enabler, bool p_enable) { - - ERR_FAIL_INDEX(p_enabler, ENABLER_MAX); - enabler[p_enabler] = p_enable; -} -bool VisibilityEnabler2D::is_enabler_enabled(Enabler p_enabler) const { - - ERR_FAIL_INDEX_V(p_enabler, ENABLER_MAX, false); - return enabler[p_enabler]; -} - -VisibilityEnabler2D::VisibilityEnabler2D() { - - for (int i = 0; i < ENABLER_MAX; i++) - enabler[i] = true; - enabler[ENABLER_PARENT_PROCESS] = false; - enabler[ENABLER_PARENT_PHYSICS_PROCESS] = false; - - visible = false; -} diff --git a/scene/2d/visible_on_screen_notifier_2d.cpp b/scene/2d/visible_on_screen_notifier_2d.cpp new file mode 100644 index 0000000000..eb4bedb6a3 --- /dev/null +++ b/scene/2d/visible_on_screen_notifier_2d.cpp @@ -0,0 +1,207 @@ +/*************************************************************************/ +/* visible_on_screen_notifier_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 "visible_on_screen_notifier_2d.h" + +#include "scene/scene_string_names.h" + +#ifdef TOOLS_ENABLED +Rect2 VisibleOnScreenNotifier2D::_edit_get_rect() const { + return rect; +} + +bool VisibleOnScreenNotifier2D::_edit_use_rect() const { + return true; +} +#endif + +void VisibleOnScreenNotifier2D::_visibility_enter() { + if (!is_inside_tree() || Engine::get_singleton()->is_editor_hint()) { + return; + } + + on_screen = true; + emit_signal(SceneStringNames::get_singleton()->screen_entered); + _screen_enter(); +} +void VisibleOnScreenNotifier2D::_visibility_exit() { + if (!is_inside_tree() || Engine::get_singleton()->is_editor_hint()) { + return; + } + + on_screen = false; + emit_signal(SceneStringNames::get_singleton()->screen_exited); + _screen_exit(); +} + +void VisibleOnScreenNotifier2D::set_rect(const Rect2 &p_rect) { + rect = p_rect; + if (is_inside_tree()) { + RS::get_singleton()->canvas_item_set_visibility_notifier(get_canvas_item(), true, rect, callable_mp(this, &VisibleOnScreenNotifier2D::_visibility_enter), callable_mp(this, &VisibleOnScreenNotifier2D::_visibility_exit)); + } +} + +Rect2 VisibleOnScreenNotifier2D::get_rect() const { + return rect; +} + +void VisibleOnScreenNotifier2D::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + //get_world_2d()-> + on_screen = false; + RS::get_singleton()->canvas_item_set_visibility_notifier(get_canvas_item(), true, rect, callable_mp(this, &VisibleOnScreenNotifier2D::_visibility_enter), callable_mp(this, &VisibleOnScreenNotifier2D::_visibility_exit)); + } break; + case NOTIFICATION_DRAW: { + if (Engine::get_singleton()->is_editor_hint()) { + draw_rect(rect, Color(1, 0.5, 1, 0.2)); + } + } break; + case NOTIFICATION_EXIT_TREE: { + on_screen = false; + RS::get_singleton()->canvas_item_set_visibility_notifier(get_canvas_item(), false, Rect2(), Callable(), Callable()); + } break; + } +} + +bool VisibleOnScreenNotifier2D::is_on_screen() const { + return on_screen; +} + +void VisibleOnScreenNotifier2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_rect", "rect"), &VisibleOnScreenNotifier2D::set_rect); + ClassDB::bind_method(D_METHOD("get_rect"), &VisibleOnScreenNotifier2D::get_rect); + ClassDB::bind_method(D_METHOD("is_on_screen"), &VisibleOnScreenNotifier2D::is_on_screen); + + ADD_PROPERTY(PropertyInfo(Variant::RECT2, "rect"), "set_rect", "get_rect"); + + ADD_SIGNAL(MethodInfo("screen_entered")); + ADD_SIGNAL(MethodInfo("screen_exited")); +} + +VisibleOnScreenNotifier2D::VisibleOnScreenNotifier2D() { + rect = Rect2(-10, -10, 20, 20); +} + +////////////////////////////////////// + +void VisibleOnScreenEnabler2D::_screen_enter() { + _update_enable_mode(true); +} + +void VisibleOnScreenEnabler2D::_screen_exit() { + _update_enable_mode(false); +} + +void VisibleOnScreenEnabler2D::set_enable_mode(EnableMode p_mode) { + enable_mode = p_mode; + if (is_inside_tree()) { + _update_enable_mode(is_on_screen()); + } +} +VisibleOnScreenEnabler2D::EnableMode VisibleOnScreenEnabler2D::get_enable_mode() { + return enable_mode; +} + +void VisibleOnScreenEnabler2D::set_enable_node_path(NodePath p_path) { + if (enable_node_path == p_path) { + return; + } + enable_node_path = p_path; + if (is_inside_tree()) { + node_id = ObjectID(); + Node *node = get_node(enable_node_path); + if (node) { + node_id = node->get_instance_id(); + _update_enable_mode(is_on_screen()); + } + } +} +NodePath VisibleOnScreenEnabler2D::get_enable_node_path() { + return enable_node_path; +} + +void VisibleOnScreenEnabler2D::_update_enable_mode(bool p_enable) { + Node *node = static_cast<Node *>(ObjectDB::get_instance(node_id)); + if (node) { + if (p_enable) { + switch (enable_mode) { + case ENABLE_MODE_INHERIT: { + node->set_process_mode(PROCESS_MODE_INHERIT); + } break; + case ENABLE_MODE_ALWAYS: { + node->set_process_mode(PROCESS_MODE_ALWAYS); + } break; + case ENABLE_MODE_WHEN_PAUSED: { + node->set_process_mode(PROCESS_MODE_WHEN_PAUSED); + } break; + } + } else { + node->set_process_mode(PROCESS_MODE_DISABLED); + } + } +} +void VisibleOnScreenEnabler2D::_notification(int p_what) { + if (p_what == NOTIFICATION_ENTER_TREE) { + if (Engine::get_singleton()->is_editor_hint()) { + return; + } + + node_id = ObjectID(); + Node *node = get_node(enable_node_path); + if (node) { + node_id = node->get_instance_id(); + node->set_process_mode(PROCESS_MODE_DISABLED); + } + } + + if (p_what == NOTIFICATION_EXIT_TREE) { + node_id = ObjectID(); + } +} + +void VisibleOnScreenEnabler2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_enable_mode", "mode"), &VisibleOnScreenEnabler2D::set_enable_mode); + ClassDB::bind_method(D_METHOD("get_enable_mode"), &VisibleOnScreenEnabler2D::get_enable_mode); + + ClassDB::bind_method(D_METHOD("set_enable_node_path", "path"), &VisibleOnScreenEnabler2D::set_enable_node_path); + ClassDB::bind_method(D_METHOD("get_enable_node_path"), &VisibleOnScreenEnabler2D::get_enable_node_path); + + ADD_GROUP("Enabling", "enable_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "enable_mode", PROPERTY_HINT_ENUM, "Inherit,Always,WhenPaused"), "set_enable_mode", "get_enable_mode"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "enable_node_path"), "set_enable_node_path", "get_enable_node_path"); + + BIND_ENUM_CONSTANT(ENABLE_MODE_INHERIT); + BIND_ENUM_CONSTANT(ENABLE_MODE_ALWAYS); + BIND_ENUM_CONSTANT(ENABLE_MODE_WHEN_PAUSED); +} + +VisibleOnScreenEnabler2D::VisibleOnScreenEnabler2D() { +} diff --git a/scene/2d/visibility_notifier_2d.h b/scene/2d/visible_on_screen_notifier_2d.h index a3b79d29e9..9c236a138f 100644 --- a/scene/2d/visibility_notifier_2d.h +++ b/scene/2d/visible_on_screen_notifier_2d.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* visibility_notifier_2d.h */ +/* visible_on_screen_notifier_2d.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,26 +28,25 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef VISIBILITY_NOTIFIER_2D_H -#define VISIBILITY_NOTIFIER_2D_H +#ifndef VISIBLE_ON_SCREEN_NOTIFIER_2D_H +#define VISIBLE_ON_SCREEN_NOTIFIER_2D_H #include "scene/2d/node_2d.h" class Viewport; -class VisibilityNotifier2D : public Node2D { - - GDCLASS(VisibilityNotifier2D, Node2D); +class VisibleOnScreenNotifier2D : public Node2D { + GDCLASS(VisibleOnScreenNotifier2D, Node2D); Set<Viewport *> viewports; Rect2 rect; -protected: - friend struct SpatialIndexer2D; - - void _enter_viewport(Viewport *p_viewport); - void _exit_viewport(Viewport *p_viewport); +private: + bool on_screen = false; + void _visibility_enter(); + void _visibility_exit(); +protected: virtual void _screen_enter() {} virtual void _screen_exit() {} @@ -56,8 +55,8 @@ protected: public: #ifdef TOOLS_ENABLED - virtual Rect2 _edit_get_rect() const; - virtual bool _edit_use_rect() const; + virtual Rect2 _edit_get_rect() const override; + virtual bool _edit_use_rect() const override; #endif void set_rect(const Rect2 &p_rect); @@ -65,50 +64,42 @@ public: bool is_on_screen() const; - VisibilityNotifier2D(); + VisibleOnScreenNotifier2D(); }; -class VisibilityEnabler2D : public VisibilityNotifier2D { - - GDCLASS(VisibilityEnabler2D, VisibilityNotifier2D); +class VisibleOnScreenEnabler2D : public VisibleOnScreenNotifier2D { + GDCLASS(VisibleOnScreenEnabler2D, VisibleOnScreenNotifier2D); public: - enum Enabler { - ENABLER_PAUSE_ANIMATIONS, - ENABLER_FREEZE_BODIES, - ENABLER_PAUSE_PARTICLES, - ENABLER_PARENT_PROCESS, - ENABLER_PARENT_PHYSICS_PROCESS, - ENABLER_PAUSE_ANIMATED_SPRITES, - ENABLER_MAX + enum EnableMode { + ENABLE_MODE_INHERIT, + ENABLE_MODE_ALWAYS, + ENABLE_MODE_WHEN_PAUSED, }; protected: - virtual void _screen_enter(); - virtual void _screen_exit(); + ObjectID node_id; + virtual void _screen_enter() override; + virtual void _screen_exit() override; - bool visible; - - void _find_nodes(Node *p_node); - - Map<Node *, Variant> nodes; - void _node_removed(Node *p_node); - bool enabler[ENABLER_MAX]; - - void _change_node_state(Node *p_node, bool p_enabled); + EnableMode enable_mode = ENABLE_MODE_INHERIT; + NodePath enable_node_path = NodePath(".."); void _notification(int p_what); static void _bind_methods(); + void _update_enable_mode(bool p_enable); + public: - void set_enabler(Enabler p_enabler, bool p_enable); - bool is_enabler_enabled(Enabler p_enabler) const; + void set_enable_mode(EnableMode p_mode); + EnableMode get_enable_mode(); - String get_configuration_warning() const; + void set_enable_node_path(NodePath p_path); + NodePath get_enable_node_path(); - VisibilityEnabler2D(); + VisibleOnScreenEnabler2D(); }; -VARIANT_ENUM_CAST(VisibilityEnabler2D::Enabler); +VARIANT_ENUM_CAST(VisibleOnScreenEnabler2D::EnableMode); #endif // VISIBILITY_NOTIFIER_2D_H |