diff options
Diffstat (limited to 'scene/2d')
72 files changed, 6774 insertions, 4424 deletions
diff --git a/scene/2d/animated_sprite_2d.cpp b/scene/2d/animated_sprite_2d.cpp index f39850441b..96a3134691 100644 --- a/scene/2d/animated_sprite_2d.cpp +++ b/scene/2d/animated_sprite_2d.cpp @@ -30,7 +30,6 @@ #include "animated_sprite_2d.h" -#include "core/os/os.h" #include "scene/main/viewport.h" #include "scene/scene_string_names.h" @@ -105,214 +104,6 @@ Rect2 AnimatedSprite2D::_get_rect() const { 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(); -} - -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; -} - -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; - } -} - -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); -} - void AnimatedSprite2D::_validate_property(PropertyInfo &property) const { if (!frames.is_valid()) { return; @@ -331,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; } } @@ -367,12 +158,12 @@ void AnimatedSprite2D::_notification(int p_what) { return; } - float speed = frames->get_animation_speed(animation) * speed_scale; + double speed = frames->get_animation_speed(animation) * speed_scale; if (speed == 0) { return; //do nothing } - float remaining = get_process_delta_time(); + double remaining = get_process_delta_time(); while (remaining) { if (timeout <= 0) { @@ -413,7 +204,7 @@ void AnimatedSprite2D::_notification(int p_what) { emit_signal(SceneStringNames::get_singleton()->frame_changed); } - float to_process = MIN(timeout, remaining); + double to_process = MIN(timeout, remaining); remaining -= to_process; timeout -= to_process; } @@ -480,7 +271,7 @@ void AnimatedSprite2D::set_sprite_frames(const Ref<SpriteFrames> &p_frames) { notify_property_list_changed(); _reset_timeout(); update(); - update_configuration_warning(); + update_configuration_warnings(); } Ref<SpriteFrames> AnimatedSprite2D::get_sprite_frames() const { @@ -518,8 +309,8 @@ int AnimatedSprite2D::get_frame() const { return frame; } -void AnimatedSprite2D::set_speed_scale(float p_speed_scale) { - float elapsed = _get_frame_duration() - timeout; +void AnimatedSprite2D::set_speed_scale(double p_speed_scale) { + double elapsed = _get_frame_duration() - timeout; speed_scale = MAX(p_speed_scale, 0.0f); @@ -528,7 +319,7 @@ void AnimatedSprite2D::set_speed_scale(float p_speed_scale) { timeout -= elapsed; } -float AnimatedSprite2D::get_speed_scale() const { +double AnimatedSprite2D::get_speed_scale() const { return speed_scale; } @@ -594,7 +385,7 @@ void AnimatedSprite2D::play(const StringName &p_animation, const bool p_backward 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); } } @@ -610,9 +401,9 @@ bool AnimatedSprite2D::is_playing() const { return playing; } -float AnimatedSprite2D::_get_frame_duration() { +double AnimatedSprite2D::_get_frame_duration() { if (frames.is_valid() && frames->has_animation(animation)) { - float speed = frames->get_animation_speed(animation) * speed_scale; + double speed = frames->get_animation_speed(animation) * speed_scale; if (speed > 0) { return 1.0 / speed; } @@ -648,17 +439,14 @@ StringName AnimatedSprite2D::get_animation() const { return animation; } -String AnimatedSprite2D::get_configuration_warning() const { - String warning = Node2D::get_configuration_warning(); +TypedArray<String> AnimatedSprite2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); if (frames.is_null()) { - if (!warning.is_empty()) { - warning += "\n\n"; - } - warning += 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 warning; + return warnings; } void AnimatedSprite2D::_bind_methods() { diff --git a/scene/2d/animated_sprite_2d.h b/scene/2d/animated_sprite_2d.h index 5e53a401e2..ac4b20a6d9 100644 --- a/scene/2d/animated_sprite_2d.h +++ b/scene/2d/animated_sprite_2d.h @@ -32,73 +32,7 @@ #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 = 5.0; - bool loop = true; - Vector<Ref<Texture2D>> frames; - }; - - 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]; - } - - 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); @@ -121,7 +55,7 @@ class AnimatedSprite2D : public Node2D { void _res_changed(); - float _get_frame_duration(); + double _get_frame_duration(); void _reset_timeout(); void _set_playing(bool p_playing); bool _is_playing() const; @@ -159,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; @@ -174,7 +108,7 @@ public: void set_flip_v(bool p_flip); bool is_flipped_v() const; - virtual String get_configuration_warning() const override; + TypedArray<String> get_configuration_warnings() const override; AnimatedSprite2D(); }; diff --git a/scene/2d/area_2d.cpp b/scene/2d/area_2d.cpp index 49d1654e3f..d78b9847e6 100644 --- a/scene/2d/area_2d.cpp +++ b/scene/2d/area_2d.cpp @@ -32,7 +32,6 @@ #include "scene/scene_string_names.h" #include "servers/audio_server.h" -#include "servers/physics_server_2d.h" void Area2D::set_space_override_mode(SpaceOverride p_mode) { space_override = p_mode; @@ -118,7 +117,7 @@ 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); } } @@ -132,7 +131,7 @@ 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); } } @@ -154,6 +153,7 @@ 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) { @@ -170,7 +170,7 @@ void Area2D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i } 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 { @@ -192,7 +192,7 @@ void Area2D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i } } if (!node || in_tree) { - emit_signal(SceneStringNames::get_singleton()->body_shape_exited, objid, obj, p_body_shape, p_area_shape); + emit_signal(SceneStringNames::get_singleton()->body_shape_exited, p_body, obj, p_body_shape, p_area_shape); } } @@ -211,7 +211,7 @@ 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); } } @@ -225,7 +225,7 @@ 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); } } @@ -246,6 +246,7 @@ 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) { @@ -262,7 +263,7 @@ void Area2D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, i } 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 { @@ -284,7 +285,7 @@ void Area2D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, i } } if (!node || in_tree) { - emit_signal(SceneStringNames::get_singleton()->area_shape_exited, objid, obj, p_area_shape, p_self_shape); + emit_signal(SceneStringNames::get_singleton()->area_shape_exited, p_area, obj, p_area_shape, p_self_shape); } } @@ -315,7 +316,7 @@ void Area2D::_clear_monitoring() { } 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); @@ -343,7 +344,7 @@ void Area2D::_clear_monitoring() { } 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); @@ -450,52 +451,6 @@ bool Area2D::overlaps_body(Node *p_body) const { 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; } @@ -557,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); @@ -590,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, "Node2D"), 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, "Node2D"), 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, "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, "local_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, "local_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"); @@ -627,7 +569,7 @@ void Area2D::_bind_methods() { Area2D::Area2D() : CollisionObject2D(PhysicsServer2D::get_singleton()->area_create(), true) { - set_gravity(98); + set_gravity(980); set_gravity_vector(Vector2(0, 1)); set_monitoring(true); set_monitorable(true); diff --git a/scene/2d/area_2d.h b/scene/2d/area_2d.h index 39b022fd2c..2c29e4660d 100644 --- a/scene/2d/area_2d.h +++ b/scene/2d/area_2d.h @@ -54,8 +54,6 @@ private: real_t gravity_distance_scale = 0.0; real_t linear_damp = 0.1; real_t angular_damp = 1.0; - uint32_t collision_mask = 1; - uint32_t collision_layer = 1; int priority = 0; bool monitoring = false; bool monitorable = false; @@ -85,6 +83,7 @@ private: }; struct BodyState { + RID rid; int rc = 0; bool in_tree = false; VSet<ShapePair> shapes; @@ -116,6 +115,7 @@ private: }; struct AreaState { + RID rid; int rc = 0; bool in_tree = false; VSet<AreaShapePair> shapes; @@ -163,18 +163,6 @@ 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; - TypedArray<Node2D> get_overlapping_bodies() const; //function for script TypedArray<Area2D> get_overlapping_areas() const; //function for script diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp index 6d8d6058eb..8401909384 100644 --- a/scene/2d/audio_stream_player_2d.cpp +++ b/scene/2d/audio_stream_player_2d.cpp @@ -30,129 +30,21 @@ #include "audio_stream_player_2d.h" -#include "core/config/engine.h" #include "scene/2d/area_2d.h" +#include "scene/2d/listener_2d.h" #include "scene/main/window.h" -void AudioStreamPlayer2D::_mix_audio() { - if (!stream_playback.is_valid() || !active.is_set() || - (stream_paused && !stream_paused_fade_out)) { - return; - } - - if (setseek.get() >= 0.0) { - stream_playback->start(setseek.get()); - setseek.set(-1.0); //reset seek - } - - //get data - AudioFrame *buffer = mix_buffer.ptrw(); - int buffer_size = mix_buffer.size(); - - if (stream_paused_fade_out) { - // Short fadeout ramp - buffer_size = MIN(buffer_size, 128); - } - - stream_playback->mix(buffer, pitch_scale, buffer_size); - - //write all outputs - int oc = output_count.get(); - for (int i = 0; i < oc; i++) { - Output current = outputs[i]; - - //see if current output exists, to keep volume ramp - bool found = false; - for (int j = i; j < prev_output_count; j++) { - if (prev_outputs[j].viewport == current.viewport) { - if (j != i) { - SWAP(prev_outputs[j], prev_outputs[i]); - } - found = true; - break; - } - } - - if (!found) { - //create new if was not used before - if (prev_output_count < MAX_OUTPUTS) { - prev_outputs[prev_output_count] = prev_outputs[i]; //may be owned by another viewport - prev_output_count++; - } - prev_outputs[i] = current; - } - - //mix! - AudioFrame target_volume = stream_paused_fade_out ? AudioFrame(0.f, 0.f) : current.vol; - AudioFrame vol_prev = stream_paused_fade_in ? AudioFrame(0.f, 0.f) : prev_outputs[i].vol; - AudioFrame vol_inc = (target_volume - vol_prev) / float(buffer_size); - AudioFrame vol = vol_prev; - - int cc = AudioServer::get_singleton()->get_channel_count(); - - if (cc == 1) { - if (!AudioServer::get_singleton()->thread_has_channel_mix_buffer(current.bus_index, 0)) { - continue; //may have been removed - } - - AudioFrame *target = AudioServer::get_singleton()->thread_get_channel_mix_buffer(current.bus_index, 0); - - for (int j = 0; j < buffer_size; j++) { - target[j] += buffer[j] * vol; - vol += vol_inc; - } - - } else { - AudioFrame *targets[4]; - bool valid = true; - - for (int k = 0; k < cc; k++) { - if (!AudioServer::get_singleton()->thread_has_channel_mix_buffer(current.bus_index, k)) { - valid = false; //may have been removed - break; - } - - targets[k] = AudioServer::get_singleton()->thread_get_channel_mix_buffer(current.bus_index, k); - } - - if (!valid) { - continue; - } - - for (int j = 0; j < buffer_size; j++) { - AudioFrame frame = buffer[j] * vol; - for (int k = 0; k < cc; k++) { - targets[k][j] += frame; - } - vol += vol_inc; - } - } - - prev_outputs[i] = current; - } - - prev_output_count = oc; - - //stream is no longer active, disable this. - if (!stream_playback->is_playing()) { - active.clear(); - } - - output_ready.clear(); - stream_paused_fade_in = false; - stream_paused_fade_out = false; -} - void AudioStreamPlayer2D::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { - AudioServer::get_singleton()->add_callback(_mix_audios, this); + AudioServer::get_singleton()->add_listener_changed_callback(_listener_changed_cb, this); if (autoplay && !Engine::get_singleton()->is_editor_hint()) { play(); } } if (p_what == NOTIFICATION_EXIT_TREE) { - AudioServer::get_singleton()->remove_callback(_mix_audios, this); + stop(); + AudioServer::get_singleton()->remove_listener_changed_callback(_listener_changed_cb, this); } if (p_what == NOTIFICATION_PAUSED) { @@ -168,120 +60,143 @@ void AudioStreamPlayer2D::_notification(int p_what) { if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { //update anything related to position first, if possible of course + if (setplay.get() > 0 || (active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count())) { + _update_panning(); + } - if (!output_ready.is_set()) { - 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); + 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); + } - //check if any area is diverting sound into a bus + if (!stream_playbacks.is_empty() && active.is_set()) { + // Stop playing if no longer active. + Vector<Ref<AudioStreamPlayback>> playbacks_to_remove; + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (playback.is_valid() && !AudioServer::get_singleton()->is_playback_active(playback) && !AudioServer::get_singleton()->is_playback_paused(playback)) { + emit_signal(SNAME("finished")); + playbacks_to_remove.push_back(playback); + } + } + // Now go through and remove playbacks that have finished. Removing elements from a Vector in a range based for is asking for trouble. + for (Ref<AudioStreamPlayback> &playback : playbacks_to_remove) { + stream_playbacks.erase(playback); + } + if (!playbacks_to_remove.is_empty() && stream_playbacks.is_empty()) { + // This node is no longer actively playing audio. + active.clear(); + set_physics_process_internal(false); + } + } - PhysicsDirectSpaceState2D *space_state = PhysicsServer2D::get_singleton()->space_get_direct_state(world_2d->get_space()); + while (stream_playbacks.size() > max_polyphony) { + AudioServer::get_singleton()->stop_playback_stream(stream_playbacks[0]); + stream_playbacks.remove(0); + } + } +} - PhysicsDirectSpaceState2D::ShapeResult sr[MAX_INTERSECT_AREAS]; +StringName AudioStreamPlayer2D::_get_actual_bus() { + Vector2 global_pos = get_global_position(); - int areas = space_state->intersect_point(global_pos, sr, MAX_INTERSECT_AREAS, Set<RID>(), area_mask, false, true); + //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")); - for (int i = 0; i < areas; i++) { - Area2D *area2d = Object::cast_to<Area2D>(sr[i].collider); - if (!area2d) { - continue; - } + PhysicsDirectSpaceState2D *space_state = PhysicsServer2D::get_singleton()->space_get_direct_state(world_2d->get_space()); + PhysicsDirectSpaceState2D::ShapeResult sr[MAX_INTERSECT_AREAS]; - if (!area2d->is_overriding_audio_bus()) { - continue; - } + int areas = space_state->intersect_point(global_pos, sr, MAX_INTERSECT_AREAS, Set<RID>(), area_mask, false, true); - StringName bus_name = area2d->get_audio_bus_name(); - bus_index = AudioServer::get_singleton()->thread_find_bus_index(bus_name); - break; - } + for (int i = 0; i < areas; i++) { + Area2D *area2d = Object::cast_to<Area2D>(sr[i].collider); + if (!area2d) { + continue; + } - world_2d->get_viewport_list(&viewports); - for (List<Viewport *>::Element *E = viewports.front(); E; E = E->next()) { - Viewport *vp = E->get(); - if (vp->is_audio_listener_2d()) { - //compute matrix to convert to screen - Transform2D to_screen = vp->get_global_canvas_transform() * vp->get_canvas_transform(); - Vector2 screen_size = vp->get_visible_rect().size; + if (!area2d->is_overriding_audio_bus()) { + continue; + } - //screen in global is used for attenuation - Vector2 screen_in_global = to_screen.affine_inverse().xform(screen_size * 0.5); + return area2d->get_audio_bus_name(); + } + return default_bus; +} - float dist = global_pos.distance_to(screen_in_global); //distance to screen center +void AudioStreamPlayer2D::_update_panning() { + if (!active.is_set() || stream.is_null()) { + return; + } - if (dist > max_distance) { - continue; //can't hear this sound in this viewport - } + Ref<World2D> world_2d = get_world_2d(); + ERR_FAIL_COND(world_2d.is_null()); - float multiplier = Math::pow(1.0f - dist / max_distance, attenuation); - multiplier *= Math::db2linear(volume_db); //also apply player volume! + Vector2 global_pos = get_global_position(); - //point in screen is used for panning - Vector2 point_in_screen = to_screen.xform(global_pos); + Set<Viewport *> viewports = world_2d->get_viewports(); + viewports.insert(get_viewport()); // TODO: This is a mediocre workaround for #50958. Remove when that bug is fixed! - float pan = CLAMP(point_in_screen.x / screen_size.width, 0.0, 1.0); + volume_vector.resize(4); + volume_vector.write[0] = AudioFrame(0, 0); + volume_vector.write[1] = AudioFrame(0, 0); + volume_vector.write[2] = AudioFrame(0, 0); + volume_vector.write[3] = AudioFrame(0, 0); - float l = 1.0 - pan; - float r = pan; + for (Viewport *vp : viewports) { + if (!vp->is_audio_listener_2d()) { + continue; + } + //compute matrix to convert to screen + Vector2 screen_size = vp->get_visible_rect().size; + Vector2 listener_in_global; + Vector2 relative_to_listener; + + //screen in global is used for attenuation + Listener2D *listener = vp->get_listener_2d(); + if (listener) { + listener_in_global = listener->get_global_position(); + relative_to_listener = global_pos - listener_in_global; + } else { + Transform2D to_listener = vp->get_global_canvas_transform() * vp->get_canvas_transform(); + listener_in_global = to_listener.affine_inverse().xform(screen_size * 0.5); + relative_to_listener = to_listener.xform(global_pos) - screen_size * 0.5; + } - outputs[new_output_count].vol = AudioFrame(l, r) * multiplier; - outputs[new_output_count].bus_index = bus_index; - outputs[new_output_count].viewport = vp; //keep pointer only for reference - new_output_count++; - if (new_output_count == MAX_OUTPUTS) { - break; - } - } - } + float dist = global_pos.distance_to(listener_in_global); // Distance to listener, or screen if none. - output_count.set(new_output_count); - output_ready.set(); + if (dist > max_distance) { + continue; //can't hear this sound in this viewport } - //start playing if requested - if (setplay.get() >= 0.0) { - setseek.set(setplay.get()); - active.set(); - setplay.set(-1); - } + float multiplier = Math::pow(1.0f - dist / max_distance, attenuation); + multiplier *= Math::db2linear(volume_db); //also apply player volume! - //stop playing if no longer active - if (!active.is_set()) { - set_physics_process_internal(false); - emit_signal("finished"); - } - } -} + float pan = CLAMP((relative_to_listener.x + screen_size.x * 0.5) / screen_size.x, 0.0, 1.0); -void AudioStreamPlayer2D::set_stream(Ref<AudioStream> p_stream) { - AudioServer::get_singleton()->lock(); + float l = 1.0 - pan; + float r = pan; - mix_buffer.resize(AudioServer::get_singleton()->thread_get_mix_buffer_size()); + volume_vector.write[0] = AudioFrame(l, r) * multiplier; + } - if (stream_playback.is_valid()) { - stream_playback.unref(); - stream.unref(); - active.clear(); - setseek.set(-1); + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_bus_exclusive(playback, _get_actual_bus(), volume_vector); } - if (p_stream.is_valid()) { - stream = p_stream; - stream_playback = p_stream->instance_playback(); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_pitch_scale(playback, pitch_scale); } - AudioServer::get_singleton()->unlock(); + last_mix_count = AudioServer::get_singleton()->get_mix_count(); +} - if (p_stream.is_valid() && stream_playback.is_null()) { - stream.unref(); - } +void AudioStreamPlayer2D::set_stream(Ref<AudioStream> p_stream) { + stop(); + stream = p_stream; } Ref<AudioStream> AudioStreamPlayer2D::get_stream() const { @@ -299,6 +214,9 @@ float AudioStreamPlayer2D::get_volume_db() const { void AudioStreamPlayer2D::set_pitch_scale(float p_pitch_scale) { ERR_FAIL_COND(p_pitch_scale <= 0.0); pitch_scale = p_pitch_scale; + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_pitch_scale(playback, p_pitch_scale); + } } float AudioStreamPlayer2D::get_pitch_scale() const { @@ -306,66 +224,64 @@ float AudioStreamPlayer2D::get_pitch_scale() const { } void AudioStreamPlayer2D::play(float p_from_pos) { - if (!is_playing()) { - // Reset the prev_output_count if the stream is stopped - prev_output_count = 0; + if (stream.is_null()) { + return; } - - if (stream_playback.is_valid()) { - setplay.set(p_from_pos); - output_ready.clear(); - set_physics_process_internal(true); + ERR_FAIL_COND_MSG(!is_inside_tree(), "Playback can only happen when a node is inside the scene tree"); + if (stream->is_monophonic() && is_playing()) { + stop(); } + + setplay.set(p_from_pos); + active.set(); + set_physics_process_internal(true); } void AudioStreamPlayer2D::seek(float p_seconds) { - if (stream_playback.is_valid()) { - setseek.set(p_seconds); + if (is_playing()) { + stop(); + play(p_seconds); } } void AudioStreamPlayer2D::stop() { - if (stream_playback.is_valid()) { - active.clear(); - set_physics_process_internal(false); - setplay.set(-1); + setplay.set(-1); + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->stop_playback_stream(playback); } + stream_playbacks.clear(); + active.clear(); + set_physics_process_internal(false); } bool AudioStreamPlayer2D::is_playing() const { - if (stream_playback.is_valid()) { - return active.is_set() || setplay.get() >= 0; + for (const Ref<AudioStreamPlayback> &playback : stream_playbacks) { + if (AudioServer::get_singleton()->is_playback_active(playback)) { + return true; + } } - return false; } float AudioStreamPlayer2D::get_playback_position() { - if (stream_playback.is_valid()) { - float ss = setseek.get(); - if (ss >= 0.0) { - return ss; - } - return stream_playback->get_playback_position(); + // Return the playback position of the most recently started playback stream. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->get_playback_position(stream_playbacks[stream_playbacks.size() - 1]); } - return 0; } void AudioStreamPlayer2D::set_bus(const StringName &p_bus) { - //if audio is active, must lock this - AudioServer::get_singleton()->lock(); - bus = p_bus; - AudioServer::get_singleton()->unlock(); + default_bus = p_bus; // This will be pushed to the audio server during the next physics timestep, which is fast enough. } StringName AudioStreamPlayer2D::get_bus() const { for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) { - if (AudioServer::get_singleton()->get_bus_name(i) == bus) { - return bus; + if (AudioServer::get_singleton()->get_bus_name(i) == default_bus) { + return default_bus; } } - return "Master"; + return SNAME("Master"); } void AudioStreamPlayer2D::set_autoplay(bool p_enable) { @@ -433,19 +349,35 @@ uint32_t AudioStreamPlayer2D::get_area_mask() const { } void AudioStreamPlayer2D::set_stream_paused(bool p_pause) { - if (p_pause != stream_paused) { - stream_paused = p_pause; - stream_paused_fade_in = !p_pause; - stream_paused_fade_out = p_pause; + // TODO this does not have perfect recall, fix that maybe? If there are zero playbacks registered with the AudioServer, this bool isn't persisted. + for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { + AudioServer::get_singleton()->set_playback_paused(playback, p_pause); } } bool AudioStreamPlayer2D::get_stream_paused() const { - return stream_paused; + // There's currently no way to pause some playback streams but not others. Check the first and don't bother looking at the rest. + if (!stream_playbacks.is_empty()) { + return AudioServer::get_singleton()->is_playback_paused(stream_playbacks[0]); + } + return false; } Ref<AudioStreamPlayback> AudioStreamPlayer2D::get_stream_playback() { - return stream_playback; + if (!stream_playbacks.is_empty()) { + return stream_playbacks[stream_playbacks.size() - 1]; + } + return nullptr; +} + +void AudioStreamPlayer2D::set_max_polyphony(int p_max_polyphony) { + if (p_max_polyphony > 0) { + max_polyphony = p_max_polyphony; + } +} + +int AudioStreamPlayer2D::get_max_polyphony() const { + return max_polyphony; } void AudioStreamPlayer2D::_bind_methods() { @@ -486,6 +418,9 @@ void AudioStreamPlayer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer2D::set_stream_paused); ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer2D::get_stream_paused); + ClassDB::bind_method(D_METHOD("set_max_polyphony", "max_polyphony"), &AudioStreamPlayer2D::set_max_polyphony); + ClassDB::bind_method(D_METHOD("get_max_polyphony"), &AudioStreamPlayer2D::get_max_polyphony); + ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer2D::get_stream_playback); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream"); @@ -494,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"); diff --git a/scene/2d/audio_stream_player_2d.h b/scene/2d/audio_stream_player_2d.h index 21f524c703..5360fd4934 100644 --- a/scene/2d/audio_stream_player_2d.h +++ b/scene/2d/audio_stream_player_2d.h @@ -31,7 +31,6 @@ #ifndef AUDIO_STREAM_PLAYER_2D_H #define AUDIO_STREAM_PLAYER_2D_H -#include "core/templates/safe_refcount.h" #include "scene/2d/node_2d.h" #include "servers/audio/audio_stream.h" #include "servers/audio_server.h" @@ -52,38 +51,31 @@ private: Viewport *viewport = nullptr; //pointer only used for reference to previous mix }; - Output outputs[MAX_OUTPUTS]; - SafeNumeric<int> output_count; - SafeFlag output_ready; - - //these are used by audio thread to have a reference of previous volumes (for ramping volume and avoiding clicks) - Output prev_outputs[MAX_OUTPUTS]; - int prev_output_count = 0; - - Ref<AudioStreamPlayback> stream_playback; + Vector<Ref<AudioStreamPlayback>> stream_playbacks; Ref<AudioStream> stream; - Vector<AudioFrame> mix_buffer; - SafeNumeric<float> setseek{ -1.0 }; - SafeFlag active; + SafeFlag active{ false }; SafeNumeric<float> setplay{ -1.0 }; + Vector<AudioFrame> volume_vector; + + uint64_t last_mix_count = -1; + float volume_db = 0.0; float pitch_scale = 1.0; bool autoplay = false; - bool stream_paused = false; - bool stream_paused_fade_in = false; - bool stream_paused_fade_out = false; - StringName bus; - - void _mix_audio(); - static void _mix_audios(void *self) { reinterpret_cast<AudioStreamPlayer2D *>(self)->_mix_audio(); } + StringName default_bus = SNAME("Master"); + int max_polyphony = 1; void _set_playing(bool p_enable); bool _is_active() const; + StringName _get_actual_bus(); + void _update_panning(); void _bus_layout_changed(); + static void _listener_changed_cb(void *self) { reinterpret_cast<AudioStreamPlayer2D *>(self)->_update_panning(); } + uint32_t area_mask = 1; float max_distance = 2000.0; @@ -128,6 +120,9 @@ public: void set_stream_paused(bool p_pause); bool get_stream_paused() const; + void set_max_polyphony(int p_max_polyphony); + int get_max_polyphony() const; + Ref<AudioStreamPlayback> get_stream_playback(); AudioStreamPlayer2D(); diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp index c5ac4e1a05..bf91ce8e65 100644 --- a/scene/2d/camera_2d.cpp +++ b/scene/2d/camera_2d.cpp @@ -30,10 +30,7 @@ #include "camera_2d.h" -#include "core/config/engine.h" -#include "core/math/math_funcs.h" -#include "scene/scene_string_names.h" -#include "servers/rendering_server.h" +#include "scene/main/window.h" void Camera2D::_update_scroll() { if (!is_inside_tree()) { @@ -99,7 +96,7 @@ Transform2D Camera2D::get_camera_transform() { Size2 screen_size = _get_camera_screen_size(); - Point2 new_camera_pos = get_global_transform().get_origin(); + Point2 new_camera_pos = get_global_position(); Point2 ret_camera_pos; if (!first) { @@ -157,7 +154,7 @@ Transform2D Camera2D::get_camera_transform() { } if (smoothing_enabled && !Engine::get_singleton()->is_editor_hint()) { - float c = smoothing * (process_callback == 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; @@ -172,26 +169,29 @@ 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[SIDE_LEFT]) { - screen_rect.position.x = limit[SIDE_LEFT]; - } - if (screen_rect.position.x + screen_rect.size.x > limit[SIDE_RIGHT]) { - screen_rect.position.x = limit[SIDE_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[SIDE_BOTTOM]) { - screen_rect.position.y = limit[SIDE_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[SIDE_TOP]) { - screen_rect.position.y = limit[SIDE_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 (screen_rect.position.y < limit[SIDE_TOP]) { + screen_rect.position.y = limit[SIDE_TOP]; + } } if (offset != Vector2()) { @@ -238,6 +238,10 @@ void Camera2D::_notification(int p_what) { viewport = get_viewport(); } + if (is_current()) { + viewport->_camera_2d_set(this); + } + canvas = get_canvas(); RID vp = viewport->get_viewport_rid(); @@ -256,6 +260,8 @@ void Camera2D::_notification(int p_what) { if (is_current()) { if (viewport && !(custom_viewport && !ObjectDB::get_instance(custom_viewport_id))) { viewport->set_canvas_transform(Transform2D()); + clear_current(); + current = true; } } remove_from_group(group_name); @@ -270,11 +276,10 @@ void Camera2D::_notification(int p_what) { } 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(); @@ -295,15 +300,14 @@ 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[SIDE_LEFT], limit[SIDE_TOP]) - camera_origin) / camera_scale, (Vector2(limit[SIDE_RIGHT], limit[SIDE_TOP]) - camera_origin) / camera_scale, @@ -317,11 +321,10 @@ 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(); @@ -392,18 +395,29 @@ Camera2D::Camera2DProcessCallback Camera2D::get_process_callback() const { void Camera2D::_make_current(Object *p_which) { if (p_which == this) { current = true; + if (is_inside_tree()) { + get_viewport()->_camera_2d_set(this); + update(); + } } else { current = false; + if (is_inside_tree()) { + if (get_viewport()->get_camera_2d() == this) { + get_viewport()->_camera_2d_set(nullptr); + } + update(); + } } } -void Camera2D::_set_current(bool p_current) { +void Camera2D::set_current(bool p_current) { if (p_current) { make_current(); + } else { + if (current) { + clear_current(); + } } - - current = p_current; - update(); } bool Camera2D::is_current() const { @@ -411,18 +425,19 @@ bool Camera2D::is_current() const { } void Camera2D::make_current() { - if (!is_inside_tree()) { - current = true; - } else { + if (is_inside_tree()) { get_tree()->call_group_flags(SceneTree::GROUP_CALL_REALTIME, group_name, "_make_current", this); + } else { + current = true; } _update_scroll(); } void Camera2D::clear_current() { - current = false; if (is_inside_tree()) { get_tree()->call_group_flags(SceneTree::GROUP_CALL_REALTIME, group_name, "_make_current", (Object *)nullptr); + } else { + current = false; } } @@ -446,13 +461,13 @@ bool Camera2D::is_limit_smoothing_enabled() const { return limit_smoothing_enabled; } -void Camera2D::set_drag_margin(Side p_side, float 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(Side p_side) const { +real_t Camera2D::get_drag_margin(Side p_side) const { ERR_FAIL_INDEX_V((int)p_side, 4, 0); return drag_margin[p_side]; } @@ -466,8 +481,8 @@ void Camera2D::force_update_scroll() { } void Camera2D::reset_smoothing() { - smoothed_camera_pos = camera_pos; _update_scroll(); + smoothed_camera_pos = camera_pos; } void Camera2D::align() { @@ -475,7 +490,7 @@ void Camera2D::align() { Size2 screen_size = _get_camera_screen_size(); - Point2 current_camera_pos = get_global_transform().get_origin(); + Point2 current_camera_pos = get_global_position(); if (anchor_mode == ANCHOR_MODE_DRAG_CENTER) { if (drag_horizontal_offset < 0) { camera_pos.x = current_camera_pos.x + screen_size.x * 0.5 * drag_margin[SIDE_RIGHT] * drag_horizontal_offset; @@ -494,7 +509,7 @@ void Camera2D::align() { _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())) { set_process_internal(true); @@ -503,7 +518,7 @@ void Camera2D::set_follow_smoothing(float p_speed) { } } -float Camera2D::get_follow_smoothing() const { +real_t Camera2D::get_follow_smoothing() const { return smoothing; } @@ -535,7 +550,7 @@ bool Camera2D::is_drag_vertical_enabled() const { return drag_vertical_enabled; } -void Camera2D::set_drag_vertical_offset(float p_offset) { +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; @@ -543,11 +558,11 @@ void Camera2D::set_drag_vertical_offset(float p_offset) { smoothed_camera_pos = old_smoothed_camera_pos; } -float Camera2D::get_drag_vertical_offset() const { +real_t Camera2D::get_drag_vertical_offset() const { return drag_vertical_offset; } -void Camera2D::set_drag_horizontal_offset(float p_offset) { +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; @@ -555,11 +570,11 @@ void Camera2D::set_drag_horizontal_offset(float p_offset) { smoothed_camera_pos = old_smoothed_camera_pos; } -float Camera2D::get_drag_horizontal_offset() const { +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; @@ -569,6 +584,7 @@ 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 { @@ -642,6 +658,12 @@ bool Camera2D::is_margin_drawing_enabled() const { return margin_drawing_enabled; } +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); @@ -652,17 +674,14 @@ void Camera2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_rotating", "rotating"), &Camera2D::set_rotating); ClassDB::bind_method(D_METHOD("is_rotating"), &Camera2D::is_rotating); - ClassDB::bind_method(D_METHOD("make_current"), &Camera2D::make_current); - ClassDB::bind_method(D_METHOD("clear_current"), &Camera2D::clear_current); - ClassDB::bind_method(D_METHOD("_make_current"), &Camera2D::_make_current); - ClassDB::bind_method(D_METHOD("_update_scroll"), &Camera2D::_update_scroll); ClassDB::bind_method(D_METHOD("set_process_callback", "mode"), &Camera2D::set_process_callback); ClassDB::bind_method(D_METHOD("get_process_callback"), &Camera2D::get_process_callback); - ClassDB::bind_method(D_METHOD("_set_current", "current"), &Camera2D::_set_current); + ClassDB::bind_method(D_METHOD("set_current", "current"), &Camera2D::set_current); ClassDB::bind_method(D_METHOD("is_current"), &Camera2D::is_current); + ClassDB::bind_method(D_METHOD("_make_current"), &Camera2D::_make_current); ClassDB::bind_method(D_METHOD("set_limit", "margin", "limit"), &Camera2D::set_limit); ClassDB::bind_method(D_METHOD("get_limit", "margin"), &Camera2D::get_limit); @@ -718,9 +737,9 @@ 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::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_"); diff --git a/scene/2d/camera_2d.h b/scene/2d/camera_2d.h index 252d2686fc..d697515547 100644 --- a/scene/2d/camera_2d.h +++ b/scene/2d/camera_2d.h @@ -32,7 +32,6 @@ #define CAMERA_2D_H #include "scene/2d/node_2d.h" -#include "scene/main/window.h" class Camera2D : public Node2D { GDCLASS(Camera2D, Node2D); @@ -65,16 +64,16 @@ protected: AnchorMode anchor_mode = ANCHOR_MODE_DRAG_CENTER; bool rotating = false; bool current = false; - float smoothing = 5.0; + real_t smoothing = 5.0; bool smoothing_enabled = false; int limit[4]; bool limit_smoothing_enabled = false; - float drag_margin[4]; + real_t drag_margin[4]; bool drag_horizontal_enabled = false; bool drag_vertical_enabled = false; - float drag_horizontal_offset = 0.0; - float drag_vertical_offset = 0.0; + 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; @@ -83,9 +82,9 @@ protected: void _update_scroll(); void _make_current(Object *p_which); - void _set_current(bool p_current); + void set_current(bool p_current); - void _set_old_smoothing(float p_enable); + void _set_old_smoothing(real_t p_enable); bool screen_drawing_enabled = true; bool limit_drawing_enabled = false; @@ -97,8 +96,10 @@ protected: 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); @@ -122,20 +123,20 @@ public: void set_drag_vertical_enabled(bool p_enabled); bool is_drag_vertical_enabled() const; - void set_drag_margin(Side p_side, float p_drag_margin); - float get_drag_margin(Side p_side) const; + void set_drag_margin(Side p_side, real_t p_drag_margin); + real_t get_drag_margin(Side p_side) const; - void set_drag_horizontal_offset(float p_offset); - float get_drag_horizontal_offset() const; + void set_drag_horizontal_offset(real_t p_offset); + real_t get_drag_horizontal_offset() const; - void set_drag_vertical_offset(float p_offset); - float get_drag_vertical_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_callback(Camera2DProcessCallback p_mode); Camera2DProcessCallback get_process_callback() const; diff --git a/scene/2d/canvas_group.cpp b/scene/2d/canvas_group.cpp index 0f0e583ea7..ee025b6dfc 100644 --- a/scene/2d/canvas_group.cpp +++ b/scene/2d/canvas_group.cpp @@ -30,7 +30,7 @@ #include "canvas_group.h" -void CanvasGroup::set_fit_margin(float p_fit_margin) { +void CanvasGroup::set_fit_margin(real_t p_fit_margin) { ERR_FAIL_COND(p_fit_margin < 0.0); fit_margin = p_fit_margin; @@ -39,11 +39,11 @@ void CanvasGroup::set_fit_margin(float p_fit_margin) { update(); } -float CanvasGroup::get_fit_margin() const { +real_t CanvasGroup::get_fit_margin() const { return fit_margin; } -void CanvasGroup::set_clear_margin(float p_clear_margin) { +void CanvasGroup::set_clear_margin(real_t p_clear_margin) { ERR_FAIL_COND(p_clear_margin < 0.0); clear_margin = p_clear_margin; @@ -52,7 +52,7 @@ void CanvasGroup::set_clear_margin(float p_clear_margin) { update(); } -float CanvasGroup::get_clear_margin() const { +real_t CanvasGroup::get_clear_margin() const { return clear_margin; } diff --git a/scene/2d/canvas_group.h b/scene/2d/canvas_group.h index cecf7c24f4..b487d7a098 100644 --- a/scene/2d/canvas_group.h +++ b/scene/2d/canvas_group.h @@ -35,19 +35,19 @@ class CanvasGroup : public Node2D { GDCLASS(CanvasGroup, Node2D) - float fit_margin = 10.0; - float clear_margin = 10.0; + real_t fit_margin = 10.0; + real_t clear_margin = 10.0; bool use_mipmaps = false; protected: static void _bind_methods(); public: - void set_fit_margin(float p_fit_margin); - float get_fit_margin() const; + void set_fit_margin(real_t p_fit_margin); + real_t get_fit_margin() const; - void set_clear_margin(float p_clear_margin); - float get_clear_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; diff --git a/scene/2d/canvas_modulate.cpp b/scene/2d/canvas_modulate.cpp index 5d5aaae505..4de99959a3 100644 --- a/scene/2d/canvas_modulate.cpp +++ b/scene/2d/canvas_modulate.cpp @@ -51,7 +51,7 @@ void CanvasModulate::_notification(int p_what) { remove_from_group("_canvas_modulate_" + itos(get_canvas().get_id())); } - update_configuration_warning(); + update_configuration_warnings(); } } @@ -73,24 +73,19 @@ Color CanvasModulate::get_color() const { return color; } -String CanvasModulate::get_configuration_warning() const { - if (!is_visible_in_tree() || !is_inside_tree()) { - return String(); - } - - String warning = Node2D::get_configuration_warning(); +TypedArray<String> CanvasModulate::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); - List<Node *> nodes; - get_tree()->get_nodes_in_group("_canvas_modulate_" + itos(get_canvas().get_id()), &nodes); + 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); - if (nodes.size() > 1) { - if (!warning.is_empty()) { - warning += "\n\n"; + 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.")); } - warning += 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."); } - return warning; + return warnings; } CanvasModulate::CanvasModulate() { diff --git a/scene/2d/canvas_modulate.h b/scene/2d/canvas_modulate.h index 4d55a5d9cb..3d85a92a11 100644 --- a/scene/2d/canvas_modulate.h +++ b/scene/2d/canvas_modulate.h @@ -46,7 +46,7 @@ public: void set_color(const Color &p_color); Color get_color() const; - String get_configuration_warning() const override; + 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 c83ed36917..5d3a538f60 100644 --- a/scene/2d/collision_object_2d.cpp +++ b/scene/2d/collision_object_2d.cpp @@ -31,7 +31,6 @@ #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) { @@ -44,16 +43,22 @@ void CollisionObject2D::_notification(int p_what) { 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(); + + if (disabled && (disable_mode != DISABLE_MODE_REMOVE)) { + _apply_disabled(); } - _update_pickable(); + 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: { @@ -67,6 +72,7 @@ void CollisionObject2D::_notification(int p_what) { case NOTIFICATION_VISIBILITY_CHANGED: { _update_pickable(); } break; + case NOTIFICATION_TRANSFORM_CHANGED: { if (only_update_transform_changes) { return; @@ -79,15 +85,22 @@ void CollisionObject2D::_notification(int p_what) { } 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()); + 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: { @@ -97,6 +110,149 @@ void CollisionObject2D::_notification(int p_what) { PhysicsServer2D::get_singleton()->body_attach_canvas_instance_id(rid, ObjectID()); } } break; + + case NOTIFICATION_DISABLED: { + _apply_disabled(); + } break; + + case NOTIFICATION_ENABLED: { + _apply_enabled(); + } break; + } +} + +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; } } @@ -325,10 +481,8 @@ bool CollisionObject2D::is_pickable() const { return pickable; } -void CollisionObject2D::_input_event(Node *p_viewport, const Ref<InputEvent> &p_input_event, int p_shape) { - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_input_event, p_viewport, p_input_event, p_shape); - } +void CollisionObject2D::_input_event_call(Viewport *p_viewport, const Ref<InputEvent> &p_input_event, int p_shape) { + GDVIRTUAL_CALL(_input_event, p_viewport, p_input_event, p_shape); emit_signal(SceneStringNames::get_singleton()->input_event, p_viewport, p_input_event, p_shape); } @@ -346,10 +500,44 @@ void CollisionObject2D::_mouse_exit() { emit_signal(SceneStringNames::get_singleton()->mouse_exited); } +void CollisionObject2D::_mouse_shape_enter(int p_shape) { + if (get_script_instance()) { + get_script_instance()->call(SceneStringNames::get_singleton()->_mouse_shape_enter, p_shape); + } + emit_signal(SceneStringNames::get_singleton()->mouse_shape_entered, p_shape); +} + +void CollisionObject2D::_mouse_shape_exit(int p_shape) { + if (get_script_instance()) { + get_script_instance()->call(SceneStringNames::get_singleton()->_mouse_shape_exit, p_shape); + } + emit_signal(SceneStringNames::get_singleton()->mouse_shape_exited, p_shape); +} + void CollisionObject2D::set_only_update_transform_changes(bool p_enable) { only_update_transform_changes = p_enable; } +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()) { return; @@ -363,22 +551,28 @@ void CollisionObject2D::_update_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.is_empty()) { - if (!warning.is_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."); + 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); @@ -401,15 +595,26 @@ 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_GROUP("Pickable", "input_"); + 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("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) { @@ -424,6 +629,7 @@ CollisionObject2D::CollisionObject2D(RID p_rid, bool 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); } } diff --git a/scene/2d/collision_object_2d.h b/scene/2d/collision_object_2d.h index e82b61d441..19abacb201 100644 --- a/scene/2d/collision_object_2d.h +++ b/scene/2d/collision_object_2d.h @@ -32,15 +32,32 @@ #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); +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 = false; + DisableMode disable_mode = DISABLE_MODE_REMOVE; + + PhysicsServer2D::BodyMode body_mode = PhysicsServer2D::BODY_MODE_STATIC; + struct ShapeData { Object *owner = nullptr; Transform2D xform; @@ -59,7 +76,10 @@ class CollisionObject2D : public Node2D { int total_subshapes = 0; Map<uint32_t, ShapeData> shapes; - bool only_update_transform_changes = false; //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); @@ -69,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); @@ -107,7 +149,7 @@ public: void set_pickable(bool p_enabled); bool is_pickable() const; - String get_configuration_warning() const override; + TypedArray<String> get_configuration_warnings() const override; _FORCE_INLINE_ RID get_rid() const { return rid; } @@ -115,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 38198c496e..8c8a292ad7 100644 --- a/scene/2d/collision_polygon_2d.cpp +++ b/scene/2d/collision_polygon_2d.cpp @@ -31,7 +31,6 @@ #include "collision_polygon_2d.h" #include "collision_object_2d.h" -#include "core/config/engine.h" #include "core/math/geometry_2d.h" #include "scene/resources/concave_polygon_shape_2d.h" #include "scene/resources/convex_polygon_shape_2d.h" @@ -204,7 +203,7 @@ void CollisionPolygon2D::set_polygon(const Vector<Point2> &p_polygon) { _update_in_shape_owner(); } update(); - update_configuration_warning(); + update_configuration_warnings(); } Vector<Point2> CollisionPolygon2D::get_polygon() const { @@ -219,7 +218,7 @@ void CollisionPolygon2D::set_build_mode(BuildMode p_mode) { _update_in_shape_owner(); } update(); - update_configuration_warning(); + update_configuration_warnings(); } CollisionPolygon2D::BuildMode CollisionPolygon2D::get_build_mode() const { @@ -240,40 +239,28 @@ bool CollisionPolygon2D::_edit_is_selected_on_click(const Point2 &p_point, doubl } #endif -String CollisionPolygon2D::get_configuration_warning() const { - String warning = Node2D::get_configuration_warning(); +TypedArray<String> CollisionPolygon2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); if (!Object::cast_to<CollisionObject2D>(get_parent())) { - if (!warning.is_empty()) { - warning += "\n\n"; - } - warning += 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.")); } int polygon_count = polygon.size(); if (polygon_count == 0) { - if (!warning.is_empty()) { - warning += "\n\n"; - } - warning += TTR("An empty CollisionPolygon2D has no effect on collision."); + 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) { - if (!warning.is_empty()) { - warning += "\n\n"; - } - warning += TTR("Invalid polygon. At least 3 points are needed in 'Solids' build mode."); + warnings.push_back(TTR("Invalid polygon. At least 3 points are needed in 'Solids' build mode.")); } } else if (polygon_count < 2) { - if (!warning.is_empty()) { - warning += "\n\n"; - } - warning += TTR("Invalid polygon. At least 2 points are needed in 'Segments' build mode."); + warnings.push_back(TTR("Invalid polygon. At least 2 points are needed in 'Segments' build mode.")); } } - return warning; + return warnings; } void CollisionPolygon2D::set_disabled(bool p_disabled) { diff --git a/scene/2d/collision_polygon_2d.h b/scene/2d/collision_polygon_2d.h index 9df9802629..6b32923010 100644 --- a/scene/2d/collision_polygon_2d.h +++ b/scene/2d/collision_polygon_2d.h @@ -32,7 +32,6 @@ #define COLLISION_POLYGON_2D_H #include "scene/2d/node_2d.h" -#include "scene/resources/shape_2d.h" class CollisionObject2D; @@ -78,7 +77,7 @@ public: void set_polygon(const Vector<Point2> &p_polygon); Vector<Point2> get_polygon() const; - virtual String get_configuration_warning() const override; + TypedArray<String> get_configuration_warnings() const override; void set_disabled(bool p_disabled); bool is_disabled() const; diff --git a/scene/2d/collision_shape_2d.cpp b/scene/2d/collision_shape_2d.cpp index 93949f741b..d52795f0d5 100644 --- a/scene/2d/collision_shape_2d.cpp +++ b/scene/2d/collision_shape_2d.cpp @@ -31,14 +31,8 @@ #include "collision_shape_2d.h" #include "collision_object_2d.h" -#include "core/config/engine.h" -#include "scene/resources/capsule_shape_2d.h" -#include "scene/resources/circle_shape_2d.h" #include "scene/resources/concave_polygon_shape_2d.h" #include "scene/resources/convex_polygon_shape_2d.h" -#include "scene/resources/line_shape_2d.h" -#include "scene/resources/rectangle_shape_2d.h" -#include "scene/resources/segment_shape_2d.h" void CollisionShape2D::_shape_changed() { update(); @@ -162,7 +156,7 @@ void CollisionShape2D::set_shape(const Ref<Shape2D> &p_shape) { shape->connect("changed", callable_mp(this, &CollisionShape2D::_shape_changed)); } - update_configuration_warning(); + update_configuration_warnings(); } Ref<Shape2D> CollisionShape2D::get_shape() const { @@ -177,19 +171,23 @@ bool CollisionShape2D::_edit_is_selected_on_click(const Point2 &p_point, double 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()) { - return TTR("Polygon-based shapes are not meant be used nor edited directly through the CollisionShape2D node. Please use the CollisionPolygon2D node instead."); + 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) { diff --git a/scene/2d/collision_shape_2d.h b/scene/2d/collision_shape_2d.h index 695d0c6657..eaf72627c8 100644 --- a/scene/2d/collision_shape_2d.h +++ b/scene/2d/collision_shape_2d.h @@ -72,7 +72,7 @@ public: void set_one_way_collision_margin(real_t p_margin); real_t get_one_way_collision_margin() const; - virtual String get_configuration_warning() const override; + 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 48acee1bc4..b836497627 100644 --- a/scene/2d/cpu_particles_2d.cpp +++ b/scene/2d/cpu_particles_2d.cpp @@ -32,9 +32,7 @@ #include "core/core_string_names.h" #include "scene/2d/gpu_particles_2d.h" -#include "scene/main/canvas_item.h" #include "scene/resources/particles_material.h" -#include "servers/rendering_server.h" void CPUParticles2D::set_emitting(bool p_emitting) { if (emitting == p_emitting) { @@ -65,7 +63,7 @@ void CPUParticles2D::set_amount(int p_amount) { particle_order.resize(p_amount); } -void CPUParticles2D::set_lifetime(float p_lifetime) { +void CPUParticles2D::set_lifetime(double p_lifetime) { ERR_FAIL_COND_MSG(p_lifetime <= 0, "Particles lifetime must be greater than 0."); lifetime = p_lifetime; } @@ -74,19 +72,19 @@ 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; } @@ -95,7 +93,7 @@ void CPUParticles2D::set_use_local_coordinates(bool 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; } @@ -107,7 +105,7 @@ int CPUParticles2D::get_amount() const { return particles.size(); } -float CPUParticles2D::get_lifetime() const { +double CPUParticles2D::get_lifetime() const { return lifetime; } @@ -115,19 +113,19 @@ 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; } @@ -135,7 +133,7 @@ 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; } @@ -244,18 +242,15 @@ bool CPUParticles2D::get_fractional_delta() const { return fractional_delta; } -String CPUParticles2D::get_configuration_warning() const { - String warnings = Node2D::get_configuration_warning(); +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.")); } } @@ -289,39 +284,45 @@ 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()) { return; @@ -413,7 +414,7 @@ void CPUParticles2D::set_emission_shape(EmissionShape p_shape) { 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; } @@ -433,7 +434,7 @@ 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; } @@ -465,33 +466,57 @@ Vector2 CPUParticles2D::get_gravity() const { return gravity; } -void CPUParticles2D::_validate_property(PropertyInfo &property) const { - if (property.name == "color" && color_ramp.is_valid()) { - property.usage = 0; - } +void CPUParticles2D::set_scale_curve_x(Ref<Curve> p_scale_curve) { + scale_curve_x = p_scale_curve; +} + +void CPUParticles2D::set_scale_curve_y(Ref<Curve> p_scale_curve) { + scale_curve_y = p_scale_curve; +} +void CPUParticles2D::set_split_scale(bool p_split_scale) { + split_scale = p_split_scale; + notify_property_list_changed(); +} + +Ref<Curve> CPUParticles2D::get_scale_curve_x() const { + return scale_curve_x; +} + +Ref<Curve> CPUParticles2D::get_scale_curve_y() const { + return scale_curve_y; +} + +bool CPUParticles2D::get_split_scale() { + return split_scale; +} + +void CPUParticles2D::_validate_property(PropertyInfo &property) const { if (property.name == "emission_sphere_radius" && emission_shape != EMISSION_SHAPE_SPHERE) { - property.usage = 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; } } @@ -502,7 +527,7 @@ static uint32_t idhash(uint32_t x) { return x; } -static float rand_from_seed(uint32_t &seed) { +static real_t rand_from_seed(uint32_t &seed) { int k; int s = int(seed); if (s == 0) { @@ -514,7 +539,7 @@ static float rand_from_seed(uint32_t &seed) { s += 2147483647; } seed = uint32_t(s); - return float(seed % uint32_t(65536)) / 65535.0; + return (seed % uint32_t(65536)) / 65535.0; } void CPUParticles2D::_update_internal() { @@ -523,7 +548,7 @@ void CPUParticles2D::_update_internal() { return; } - float delta = get_process_delta_time(); + double delta = get_process_delta_time(); if (emitting) { inactive_time = 0; } else { @@ -543,14 +568,14 @@ void CPUParticles2D::_update_internal() { _set_redraw(true); if (time == 0 && pre_process_time > 0.0) { - float frame_time; + double frame_time; if (fixed_fps > 0) { frame_time = 1.0 / fixed_fps; } else { frame_time = 1.0 / 30.0; } - float todo = pre_process_time; + double todo = pre_process_time; while (todo >= 0) { _particles_process(frame_time); @@ -559,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); @@ -584,7 +609,7 @@ void CPUParticles2D::_update_internal() { _update_particle_data_buffer(); } -void CPUParticles2D::_particles_process(float p_delta) { +void CPUParticles2D::_particles_process(double p_delta) { p_delta *= speed_scale; int pcount = particles.size(); @@ -592,7 +617,7 @@ void CPUParticles2D::_particles_process(float p_delta) { Particle *parray = w; - float prev_time = time; + double prev_time = time; time += p_delta; if (time > lifetime) { time = Math::fmod(time, lifetime); @@ -611,7 +636,7 @@ void CPUParticles2D::_particles_process(float p_delta) { velocity_xform[2] = Vector2(); } - float system_phase = time / lifetime; + double system_phase = time / lifetime; for (int i = 0; i < pcount; i++) { Particle &p = parray[i]; @@ -620,12 +645,12 @@ void CPUParticles2D::_particles_process(float p_delta) { continue; } - float local_delta = p_delta; + double local_delta = p_delta; // The phase is a ratio between 0 (birth) and 1 (end of life) for each particle. // While we use time in tests later on, for randomness we use the phase as done in the // original shader code, and we later multiply by lifetime to get the time. - float restart_phase = float(i) / float(pcount); + double restart_phase = double(i) / double(pcount); if (randomness_ratio > 0.0) { uint32_t seed = cycle; @@ -634,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) { @@ -680,17 +705,17 @@ void CPUParticles2D::_particles_process(float p_delta) { } p.active = true; - /*float tex_linear_velocity = 0; + /*real_t tex_linear_velocity = 0; if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { tex_linear_velocity = curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY]->interpolate(0); }*/ - float tex_angle = 0.0; + real_t tex_angle = 0.0; if (curve_parameters[PARAM_ANGLE].is_valid()) { tex_angle = curve_parameters[PARAM_ANGLE]->interpolate(tv); } - float tex_anim_offset = 0.0; + real_t tex_anim_offset = 0.0; if (curve_parameters[PARAM_ANGLE].is_valid()) { tex_anim_offset = curve_parameters[PARAM_ANGLE]->interpolate(tv); } @@ -702,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::deg2rad((Math::randf() * 2.0 - 1.0) * spread); + real_t angle1_rad = Math::atan2(direction.y, direction.x) + Math::deg2rad((Math::randf() * 2.0 - 1.0) * spread); 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; @@ -723,8 +748,8 @@ void CPUParticles2D::_particles_process(float p_delta) { //do none } break; case EMISSION_SHAPE_SPHERE: { - float s = Math::randf(), t = Math_TAU * 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: { @@ -775,51 +800,51 @@ void CPUParticles2D::_particles_process(float p_delta) { p.custom[1] = p.time / lifetime; tv = p.time / p.lifetime; - float tex_linear_velocity = 0.0; + real_t tex_linear_velocity = 1.0; if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { tex_linear_velocity = curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY]->interpolate(tv); } - float tex_orbit_velocity = 0.0; + real_t tex_orbit_velocity = 1.0; if (curve_parameters[PARAM_ORBIT_VELOCITY].is_valid()) { tex_orbit_velocity = curve_parameters[PARAM_ORBIT_VELOCITY]->interpolate(tv); } - float tex_angular_velocity = 0.0; + real_t tex_angular_velocity = 1.0; if (curve_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) { tex_angular_velocity = curve_parameters[PARAM_ANGULAR_VELOCITY]->interpolate(tv); } - float tex_linear_accel = 0.0; + real_t tex_linear_accel = 1.0; if (curve_parameters[PARAM_LINEAR_ACCEL].is_valid()) { tex_linear_accel = curve_parameters[PARAM_LINEAR_ACCEL]->interpolate(tv); } - float tex_tangential_accel = 0.0; + real_t tex_tangential_accel = 1.0; if (curve_parameters[PARAM_TANGENTIAL_ACCEL].is_valid()) { tex_tangential_accel = curve_parameters[PARAM_TANGENTIAL_ACCEL]->interpolate(tv); } - float tex_radial_accel = 0.0; + real_t tex_radial_accel = 1.0; if (curve_parameters[PARAM_RADIAL_ACCEL].is_valid()) { tex_radial_accel = curve_parameters[PARAM_RADIAL_ACCEL]->interpolate(tv); } - float tex_damping = 0.0; + real_t tex_damping = 1.0; if (curve_parameters[PARAM_DAMPING].is_valid()) { tex_damping = curve_parameters[PARAM_DAMPING]->interpolate(tv); } - float tex_angle = 0.0; + real_t tex_angle = 1.0; if (curve_parameters[PARAM_ANGLE].is_valid()) { tex_angle = curve_parameters[PARAM_ANGLE]->interpolate(tv); } - float tex_anim_speed = 0.0; + real_t tex_anim_speed = 1.0; if (curve_parameters[PARAM_ANIM_SPEED].is_valid()) { tex_anim_speed = curve_parameters[PARAM_ANIM_SPEED]->interpolate(tv); } - float tex_anim_offset = 0.0; + real_t tex_anim_offset = 1.0; if (curve_parameters[PARAM_ANIM_OFFSET].is_valid()) { tex_anim_offset = curve_parameters[PARAM_ANIM_OFFSET]->interpolate(tv); } @@ -828,20 +853,20 @@ void CPUParticles2D::_particles_process(float p_delta) { Vector2 pos = p.transform[2]; //apply linear acceleration - force += p.velocity.length() > 0.0 ? p.velocity.normalized() * (parameters[PARAM_LINEAR_ACCEL] + tex_linear_accel) * Math::lerp(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_TAU; + real_t ang = orbit_amount * local_delta * Math_TAU; // Not sure why the ParticlesMaterial code uses a clockwise rotation matrix, // but we use -ang here to reproduce its behavior. Transform2D rot = Transform2D(-ang, Vector2()); @@ -852,9 +877,9 @@ void CPUParticles2D::_particles_process(float p_delta) { p.velocity = p.velocity.normalized() * tex_linear_velocity; } - if (parameters[PARAM_DAMPING] + tex_damping > 0.0) { - 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(); @@ -862,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(tv); + Vector2 tex_scale = Vector2(1.0, 1.0); + if (split_scale) { + if (scale_curve_x.is_valid()) { + tex_scale.x = scale_curve_x->interpolate(tv); + } else { + tex_scale.x = 1.0; + } + if (scale_curve_y.is_valid()) { + tex_scale.y = scale_curve_y->interpolate(tv); + } else { + tex_scale.y = 1.0; + } + } else { + if (curve_parameters[PARAM_SCALE].is_valid()) { + real_t tmp_scale = curve_parameters[PARAM_SCALE]->interpolate(tv); + tex_scale.x = tmp_scale; + tex_scale.y = tmp_scale; + } } - float tex_hue_variation = 0.0; + real_t tex_hue_variation = 0.0; if (curve_parameters[PARAM_HUE_VARIATION].is_valid()) { tex_hue_variation = curve_parameters[PARAM_HUE_VARIATION]->interpolate(tv); } - float hue_rot_angle = (parameters[PARAM_HUE_VARIATION] + tex_hue_variation) * Math_TAU * Math::lerp(1.0f, p.hue_rot_rand * 2.0f - 1.0f, randomness[PARAM_HUE_VARIATION]); - float hue_rot_c = Math::cos(hue_rot_angle); - float hue_rot_s = Math::sin(hue_rot_angle); + real_t hue_rot_angle = (tex_hue_variation)*Math_TAU * Math::lerp(parameters_min[PARAM_HUE_VARIATION], parameters_max[PARAM_HUE_VARIATION], p.hue_rot_rand); + real_t hue_rot_c = Math::cos(hue_rot_angle); + real_t hue_rot_s = Math::sin(hue_rot_angle); Basis hue_rot_mat; { @@ -921,13 +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; + Vector2 base_scale = tex_scale * Math::lerp(parameters_min[PARAM_SCALE], parameters_max[PARAM_SCALE], p.scale_rand); + if (base_scale.x < 0.00001) { + base_scale.x = 0.00001; } - - p.transform.elements[0] *= base_scale; - p.transform.elements[1] *= base_scale; + if (base_scale.y < 0.00001) { + base_scale.y = 0.00001; + } + p.transform.elements[0] *= base_scale.x; + p.transform.elements[1] *= base_scale.y; p.transform[2] += p.velocity * local_delta; } @@ -979,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; @@ -1083,7 +1124,7 @@ void CPUParticles2D::_notification(int p_what) { ptr[7] = t.elements[2][1]; } else { - zeromem(ptr, sizeof(float) * 8); + memset(ptr, 0, sizeof(float) * 8); } ptr += 16; @@ -1139,18 +1180,24 @@ void CPUParticles2D::convert_from_particles(Node *p_particles) { Vector2 rect_extents = Vector2(material->get_emission_box_extents().x, material->get_emission_box_extents().y); set_emission_rect_extents(rect_extents); + Ref<CurveXYZTexture> scale3D = material->get_param_texture(ParticlesMaterial::PARAM_SCALE); + if (scale3D.is_valid()) { + split_scale = true; + scale_curve_x = scale3D->get_curve_x(); + scale_curve_y = scale3D->get_curve_y(); + } Vector2 gravity = Vector2(material->get_gravity().x, material->get_gravity().y); set_gravity(gravity); set_lifetime_randomness(material->get_lifetime_randomness()); #define CONVERT_PARAM(m_param) \ - set_param(m_param, material->get_param(ParticlesMaterial::m_param)); \ + set_param_min(m_param, material->get_param_min(ParticlesMaterial::m_param)); \ { \ Ref<CurveTexture> ctex = material->get_param_texture(ParticlesMaterial::m_param); \ if (ctex.is_valid()) \ set_param_curve(m_param, ctex->get_curve()); \ } \ - set_param_randomness(m_param, material->get_param_randomness(ParticlesMaterial::m_param)); + set_param_max(m_param, material->get_param_max(ParticlesMaterial::m_param)); CONVERT_PARAM(PARAM_INITIAL_LINEAR_VELOCITY); CONVERT_PARAM(PARAM_ANGULAR_VELOCITY); @@ -1205,7 +1252,7 @@ void CPUParticles2D::_bind_methods() { 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"); @@ -1233,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); @@ -1272,6 +1319,15 @@ void CPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_gravity"), &CPUParticles2D::get_gravity); ClassDB::bind_method(D_METHOD("set_gravity", "accel_vec"), &CPUParticles2D::set_gravity); + ClassDB::bind_method(D_METHOD("get_split_scale"), &CPUParticles2D::get_split_scale); + ClassDB::bind_method(D_METHOD("set_split_scale", "split_scale"), &CPUParticles2D::set_split_scale); + + ClassDB::bind_method(D_METHOD("get_scale_curve_x"), &CPUParticles2D::get_scale_curve_x); + ClassDB::bind_method(D_METHOD("set_scale_curve_x", "scale_curve"), &CPUParticles2D::set_scale_curve_x); + + ClassDB::bind_method(D_METHOD("get_scale_curve_y"), &CPUParticles2D::get_scale_curve_y); + ClassDB::bind_method(D_METHOD("set_scale_curve_y", "scale_curve"), &CPUParticles2D::set_scale_curve_y); + ClassDB::bind_method(D_METHOD("convert_from_particles", "particles"), &CPUParticles2D::convert_from_particles); ADD_GROUP("Emission Shape", "emission_"); @@ -1289,54 +1345,58 @@ void CPUParticles2D::_bind_methods() { ADD_GROUP("Gravity", ""); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "gravity"), "set_gravity", "get_gravity"); ADD_GROUP("Initial Velocity", "initial_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param", "get_param", PARAM_INITIAL_LINEAR_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_INITIAL_LINEAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_min", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_min", "get_param_min", PARAM_INITIAL_LINEAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_max", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_max", "get_param_max", PARAM_INITIAL_LINEAR_VELOCITY); ADD_GROUP("Angular Velocity", "angular_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ANGULAR_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGULAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_min", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_ANGULAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_max", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_ANGULAR_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angular_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANGULAR_VELOCITY); ADD_GROUP("Orbit Velocity", "orbit_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ORBIT_VELOCITY); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ORBIT_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_min", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_ORBIT_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_max", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_ORBIT_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "orbit_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ORBIT_VELOCITY); ADD_GROUP("Linear Accel", "linear_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_LINEAR_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_LINEAR_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_LINEAR_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_LINEAR_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "linear_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_LINEAR_ACCEL); ADD_GROUP("Radial Accel", "radial_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_RADIAL_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_RADIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_RADIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_RADIAL_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "radial_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_RADIAL_ACCEL); ADD_GROUP("Tangential Accel", "tangential_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_TANGENTIAL_ACCEL); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_TANGENTIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_TANGENTIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_TANGENTIAL_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "tangential_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_TANGENTIAL_ACCEL); ADD_GROUP("Damping", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_param", "get_param", PARAM_DAMPING); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_min", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_param_min", "get_param_min", PARAM_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_max", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_param_max", "get_param_max", PARAM_DAMPING); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_DAMPING); ADD_GROUP("Angle", ""); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater"), "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); @@ -1375,22 +1435,31 @@ CPUParticles2D::CPUParticles2D() { set_amount(8); set_use_local_coordinates(true); - set_param(PARAM_INITIAL_LINEAR_VELOCITY, 0); - set_param(PARAM_ANGULAR_VELOCITY, 0); - set_param(PARAM_ORBIT_VELOCITY, 0); - set_param(PARAM_LINEAR_ACCEL, 0); - set_param(PARAM_RADIAL_ACCEL, 0); - set_param(PARAM_TANGENTIAL_ACCEL, 0); - set_param(PARAM_DAMPING, 0); - set_param(PARAM_ANGLE, 0); - set_param(PARAM_SCALE, 1); - set_param(PARAM_HUE_VARIATION, 0); - set_param(PARAM_ANIM_SPEED, 0); - set_param(PARAM_ANIM_OFFSET, 0); - - for (int i = 0; i < PARAM_MAX; i++) { - set_param_randomness(Parameter(i), 0); - } + set_param_min(PARAM_INITIAL_LINEAR_VELOCITY, 0); + set_param_min(PARAM_ANGULAR_VELOCITY, 0); + set_param_min(PARAM_ORBIT_VELOCITY, 0); + set_param_min(PARAM_LINEAR_ACCEL, 0); + set_param_min(PARAM_RADIAL_ACCEL, 0); + set_param_min(PARAM_TANGENTIAL_ACCEL, 0); + set_param_min(PARAM_DAMPING, 0); + set_param_min(PARAM_ANGLE, 0); + set_param_min(PARAM_SCALE, 1); + set_param_min(PARAM_HUE_VARIATION, 0); + set_param_min(PARAM_ANIM_SPEED, 0); + set_param_min(PARAM_ANIM_OFFSET, 0); + + set_param_max(PARAM_INITIAL_LINEAR_VELOCITY, 0); + set_param_max(PARAM_ANGULAR_VELOCITY, 0); + set_param_max(PARAM_ORBIT_VELOCITY, 0); + set_param_max(PARAM_LINEAR_ACCEL, 0); + set_param_max(PARAM_RADIAL_ACCEL, 0); + set_param_max(PARAM_TANGENTIAL_ACCEL, 0); + set_param_max(PARAM_DAMPING, 0); + set_param_max(PARAM_ANGLE, 0); + set_param_max(PARAM_SCALE, 1); + set_param_max(PARAM_HUE_VARIATION, 0); + set_param_max(PARAM_ANIM_SPEED, 0); + set_param_max(PARAM_ANIM_OFFSET, 0); for (int i = 0; i < PARTICLE_FLAG_MAX; i++) { particle_flags[i] = false; diff --git a/scene/2d/cpu_particles_2d.h b/scene/2d/cpu_particles_2d.h index 7ee165b3e1..4990d443e3 100644 --- a/scene/2d/cpu_particles_2d.h +++ b/scene/2d/cpu_particles_2d.h @@ -31,9 +31,7 @@ #ifndef CPU_PARTICLES_2D_H #define CPU_PARTICLES_2D_H -#include "core/templates/rid.h" #include "scene/2d/node_2d.h" -#include "scene/resources/texture.h" class CPUParticles2D : public Node2D { private: @@ -83,24 +81,24 @@ private: struct Particle { Transform2D transform; Color color; - float custom[4] = {}; - float rotation = 0.0; + real_t custom[4] = {}; + real_t rotation = 0.0; Vector2 velocity; bool active = false; - float angle_rand = 0.0; - float scale_rand = 0.0; - float hue_rot_rand = 0.0; - float anim_offset_rand = 0.0; - float time = 0.0; - float lifetime = 0.0; + real_t angle_rand = 0.0; + real_t scale_rand = 0.0; + real_t hue_rot_rand = 0.0; + real_t anim_offset_rand = 0.0; + double time = 0.0; + double lifetime = 0.0; Color base_color; uint32_t seed = 0; }; - float time = 0.0; - float inactive_time = 0.0; - float frame_remainder = 0.0; + double time = 0.0; + double inactive_time = 0.0; + double frame_remainder = 0.0; int cycle = 0; bool redraw = false; @@ -131,12 +129,12 @@ private: bool one_shot = false; - float lifetime = 1.0; - float pre_process_time = 0.0; - float explosiveness_ratio = 0.0; - float randomness_ratio = 0.0; - float lifetime_randomness = 0.0; - float speed_scale = 1.0; + double lifetime = 1.0; + double pre_process_time = 0.0; + real_t explosiveness_ratio = 0.0; + real_t randomness_ratio = 0.0; + double lifetime_randomness = 0.0; + double speed_scale = 1.0; bool local_coords; int fixed_fps = 0; bool fractional_delta = true; @@ -150,10 +148,10 @@ private: //////// Vector2 direction = Vector2(1, 0); - float spread = 45.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; @@ -162,17 +160,21 @@ private: bool particle_flags[PARTICLE_FLAG_MAX]; EmissionShape emission_shape = EMISSION_SHAPE_POINT; - float emission_sphere_radius = 1.0; + real_t emission_sphere_radius = 1.0; Vector2 emission_rect_extents = Vector2(1, 1); Vector<Vector2> emission_points; Vector<Vector2> emission_normals; Vector<Color> emission_colors; int emission_point_count = 0; - Vector2 gravity = Vector2(0, 98); + Ref<Curve> scale_curve_x; + Ref<Curve> scale_curve_y; + bool split_scale = false; + + Vector2 gravity = Vector2(0, 980); void _update_internal(); - void _particles_process(float p_delta); + void _particles_process(double p_delta); void _update_particle_data_buffer(); Mutex update_mutex; @@ -193,27 +195,27 @@ protected: public: void set_emitting(bool p_emitting); void set_amount(int p_amount); - void set_lifetime(float p_lifetime); + void set_lifetime(double p_lifetime); void set_one_shot(bool p_one_shot); - void set_pre_process_time(float p_time); - void set_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; @@ -235,14 +237,14 @@ public: 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; @@ -257,25 +259,31 @@ public: bool get_particle_flag(ParticleFlags p_particle_flag) const; void set_emission_shape(EmissionShape p_shape); - void set_emission_sphere_radius(float p_radius); + void set_emission_sphere_radius(real_t p_radius); void set_emission_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 override; + TypedArray<String> get_configuration_warnings() const override; void restart(); diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp index af70c47f7c..5bce705dd5 100644 --- a/scene/2d/gpu_particles_2d.cpp +++ b/scene/2d/gpu_particles_2d.cpp @@ -30,9 +30,7 @@ #include "gpu_particles_2d.h" -#include "core/os/os.h" #include "scene/resources/particles_material.h" -#include "scene/scene_string_names.h" #ifdef TOOLS_ENABLED #include "core/config/engine.h" @@ -54,7 +52,7 @@ void GPUParticles2D::set_amount(int p_amount) { RS::get_singleton()->particles_set_amount(particles, amount); } -void GPUParticles2D::set_lifetime(float p_lifetime) { +void GPUParticles2D::set_lifetime(double p_lifetime) { ERR_FAIL_COND_MSG(p_lifetime <= 0, "Particles lifetime must be greater than 0."); lifetime = p_lifetime; RS::get_singleton()->particles_set_lifetime(particles, lifetime); @@ -76,17 +74,17 @@ void GPUParticles2D::set_one_shot(bool p_enable) { } } -void GPUParticles2D::set_pre_process_time(float p_time) { +void GPUParticles2D::set_pre_process_time(double p_time) { pre_process_time = p_time; RS::get_singleton()->particles_set_pre_process_time(particles, pre_process_time); } -void GPUParticles2D::set_explosiveness_ratio(float p_ratio) { +void GPUParticles2D::set_explosiveness_ratio(real_t p_ratio) { explosiveness_ratio = p_ratio; RS::get_singleton()->particles_set_explosiveness_ratio(particles, explosiveness_ratio); } -void GPUParticles2D::set_randomness_ratio(float p_ratio) { +void GPUParticles2D::set_randomness_ratio(real_t p_ratio) { randomness_ratio = p_ratio; RS::get_singleton()->particles_set_randomness_ratio(particles, randomness_ratio); } @@ -115,7 +113,7 @@ 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)); @@ -137,10 +135,69 @@ void GPUParticles2D::set_process_material(const Ref<Material> &p_material) { } RS::get_singleton()->particles_set_process_material(particles, material_rid); - update_configuration_warning(); + update_configuration_warnings(); } -void GPUParticles2D::set_speed_scale(float p_scale) { +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_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); } @@ -153,23 +210,30 @@ int GPUParticles2D::get_amount() const { return amount; } -float GPUParticles2D::get_lifetime() const { +double GPUParticles2D::get_lifetime() const { return lifetime; } +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; } @@ -185,7 +249,7 @@ Ref<Material> GPUParticles2D::get_process_material() const { return process_material; } -float GPUParticles2D::get_speed_scale() const { +double GPUParticles2D::get_speed_scale() const { return speed_scale; } @@ -216,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 = Node2D::get_configuration_warning(); - 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.")); } } } @@ -259,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,14 +336,123 @@ void GPUParticles2D::restart() { void GPUParticles2D::_notification(int p_what) { if (p_what == NOTIFICATION_DRAW) { RID texture_rid; + Size2 size; if (texture.is_valid()) { texture_rid = texture->get_rid(); + size = texture->get_size(); + } else { + size = Size2(1, 1); } + 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); + } + } + + 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_a_parent_of(this))) { + 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 @@ -324,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); @@ -338,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); @@ -349,8 +519,20 @@ void GPUParticles2D::_bind_methods() { 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"); @@ -360,10 +542,17 @@ 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", ""); @@ -371,10 +560,16 @@ void GPUParticles2D::_bind_methods() { 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); @@ -388,10 +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 774cef9cc9..d7eee461b4 100644 --- a/scene/2d/gpu_particles_2d.h +++ b/scene/2d/gpu_particles_2d.h @@ -31,9 +31,7 @@ #ifndef PARTICLES_2D_H #define PARTICLES_2D_H -#include "core/templates/rid.h" #include "scene/2d/node_2d.h" -#include "scene/resources/texture.h" class GPUParticles2D : public Node2D { private: @@ -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; @@ -68,35 +67,58 @@ private: 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 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; @@ -110,7 +132,7 @@ public: void set_texture(const Ref<Texture2D> &p_texture); Ref<Texture2D> get_texture() const; - virtual String get_configuration_warning() const override; + 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 7d9cdd52ac..4a6606256e 100644 --- a/scene/2d/joints_2d.cpp +++ b/scene/2d/joints_2d.cpp @@ -30,10 +30,8 @@ #include "joints_2d.h" -#include "core/config/engine.h" #include "physics_body_2d.h" #include "scene/scene_string_names.h" -#include "servers/physics_server_2d.h" void Joint2D::_disconnect_signals() { Node *node_a = get_node_or_null(a); @@ -66,6 +64,7 @@ void Joint2D::_update_joint(bool p_only_free) { if (p_only_free || !is_inside_tree()) { PhysicsServer2D::get_singleton()->joint_clear(joint); warning = String(); + update_configuration_warnings(); return; } @@ -76,43 +75,26 @@ void Joint2D::_update_joint(bool p_only_free) { PhysicsBody2D *body_b = Object::cast_to<PhysicsBody2D>(node_b); if (node_a && !body_a && node_b && !body_b) { - PhysicsServer2D::get_singleton()->joint_clear(joint); warning = TTR("Node A and Node B must be PhysicsBody2Ds"); - update_configuration_warning(); - return; - } - - if (node_a && !body_a) { - PhysicsServer2D::get_singleton()->joint_clear(joint); + } else if (node_a && !body_a) { warning = TTR("Node A must be a PhysicsBody2D"); - update_configuration_warning(); - return; - } - - if (node_b && !body_b) { - PhysicsServer2D::get_singleton()->joint_clear(joint); + } else if (node_b && !body_b) { warning = TTR("Node B must be a PhysicsBody2D"); - update_configuration_warning(); - return; - } - - if (!body_a || !body_b) { - PhysicsServer2D::get_singleton()->joint_clear(joint); + } else if (!body_a || !body_b) { warning = TTR("Joint is not connected to two PhysicsBody2Ds"); - update_configuration_warning(); - return; + } else if (body_a == body_b) { + warning = TTR("Node A and Node B must be different PhysicsBody2Ds"); + } else { + warning = String(); } - if (body_a == body_b) { + update_configuration_warnings(); + + if (!warning.is_empty()) { PhysicsServer2D::get_singleton()->joint_clear(joint); - warning = TTR("Node A and Node B must be different PhysicsBody2Ds"); - update_configuration_warning(); return; } - warning = String(); - update_configuration_warning(); - if (body_a) { body_a->force_update_transform(); } @@ -211,17 +193,14 @@ bool Joint2D::get_exclude_nodes_from_collision() const { return exclude_from_collision; } -String Joint2D::get_configuration_warning() const { - String node_warning = Node2D::get_configuration_warning(); +TypedArray<String> Joint2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node2D::get_configuration_warnings(); if (!warning.is_empty()) { - if (!node_warning.is_empty()) { - node_warning += "\n\n"; - } - node_warning += warning; + warnings.push_back(warning); } - return node_warning; + return warnings; } void Joint2D::_bind_methods() { @@ -273,7 +252,7 @@ void PinJoint2D::_notification(int p_what) { } void PinJoint2D::_configure_joint(RID p_joint, PhysicsBody2D *body_a, PhysicsBody2D *body_b) { - PhysicsServer2D::get_singleton()->joint_make_pin(p_joint, get_global_transform().get_origin(), body_a->get_rid(), body_b ? body_b->get_rid() : RID()); + PhysicsServer2D::get_singleton()->joint_make_pin(p_joint, get_global_position(), body_a->get_rid(), body_b ? body_b->get_rid() : RID()); PhysicsServer2D::get_singleton()->pin_joint_set_param(p_joint, PhysicsServer2D::PIN_JOINT_SOFTNESS, softness); } @@ -293,7 +272,7 @@ 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() { @@ -355,8 +334,8 @@ void GrooveJoint2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_initial_offset", "offset"), &GrooveJoint2D::set_initial_offset); ClassDB::bind_method(D_METHOD("get_initial_offset"), &GrooveJoint2D::get_initial_offset); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_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() { @@ -452,10 +431,10 @@ 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() { diff --git a/scene/2d/joints_2d.h b/scene/2d/joints_2d.h index 08e02ee29d..dc5a08f815 100644 --- a/scene/2d/joints_2d.h +++ b/scene/2d/joints_2d.h @@ -62,7 +62,7 @@ protected: _FORCE_INLINE_ bool is_configured() const { return configured; } public: - virtual String get_configuration_warning() const override; + virtual TypedArray<String> get_configuration_warnings() const override; void set_node_a(const NodePath &p_node_a); NodePath get_node_a() const; diff --git a/scene/2d/light_2d.cpp b/scene/2d/light_2d.cpp index 15fcb08422..1853b3428c 100644 --- a/scene/2d/light_2d.cpp +++ b/scene/2d/light_2d.cpp @@ -30,9 +30,6 @@ #include "light_2d.h" -#include "core/config/engine.h" -#include "servers/rendering_server.h" - void Light2D::_update_light_visibility() { if (!is_inside_tree()) { return; @@ -84,21 +81,21 @@ 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; } @@ -159,6 +156,7 @@ int Light2D::get_item_shadow_cull_mask() const { 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 { @@ -212,15 +210,21 @@ void Light2D::_notification(int p_what) { } } -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::_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); @@ -274,7 +278,7 @@ void Light2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editor_only"), "set_editor_only", "is_editor_only"); 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, "blend_mode", PROPERTY_HINT_ENUM, "Add,Sub,Mix"), "set_blend_mode", "get_blend_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::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"); @@ -285,7 +289,7 @@ 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_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"); @@ -366,7 +370,7 @@ void PointLight2D::set_texture(const Ref<Texture2D> &p_texture) { RS::get_singleton()->canvas_light_set_texture(_get_light(), RID()); } - update_configuration_warning(); + update_configuration_warnings(); } Ref<Texture2D> PointLight2D::get_texture() const { @@ -383,20 +387,17 @@ Vector2 PointLight2D::get_texture_offset() const { return texture_offset; } -String PointLight2D::get_configuration_warning() const { - String warning = Node2D::get_configuration_warning(); +TypedArray<String> PointLight2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); if (!texture.is_valid()) { - if (!warning.is_empty()) { - warning += "\n\n"; - } - warning += TTR("A texture with the shape of the light must be supplied to the \"Texture\" property."); + warnings.push_back(TTR("A texture with the shape of the light must be supplied to the \"Texture\" property.")); } - return warning; + return warnings; } -void PointLight2D::set_texture_scale(float p_scale) { +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) { @@ -406,7 +407,7 @@ void PointLight2D::set_texture_scale(float p_scale) { item_rect_changed(); } -float PointLight2D::get_texture_scale() const { +real_t PointLight2D::get_texture_scale() const { return _scale; } @@ -432,12 +433,12 @@ PointLight2D::PointLight2D() { ////////// -void DirectionalLight2D::set_max_distance(float p_distance) { +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); } -float DirectionalLight2D::get_max_distance() const { +real_t DirectionalLight2D::get_max_distance() const { return max_distance; } diff --git a/scene/2d/light_2d.h b/scene/2d/light_2d.h index 4279baf15b..d9ecd81f1c 100644 --- a/scene/2d/light_2d.h +++ b/scene/2d/light_2d.h @@ -57,15 +57,15 @@ private: bool shadow = false; Color color = Color(1, 1, 1); Color shadow_color = Color(0, 0, 0, 0); - float height = 0.0; - float energy = 1.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; - float shadow_smooth = 0.0; + real_t shadow_smooth = 0.0; Ref<Texture2D> texture; Vector2 texture_offset; ShadowFilter shadow_filter = SHADOW_FILTER_NONE; @@ -77,6 +77,7 @@ 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: void set_enabled(bool p_enabled); @@ -88,11 +89,11 @@ public: void set_color(const Color &p_color); Color get_color() const; - void set_height(float p_height); - float get_height() const; + void set_height(real_t p_height); + real_t get_height() const; - void set_energy(float p_energy); - float get_energy() 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; @@ -121,8 +122,8 @@ public: 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; void set_blend_mode(BlendMode p_mode); BlendMode get_blend_mode() const; @@ -138,7 +139,7 @@ class PointLight2D : public Light2D { GDCLASS(PointLight2D, Light2D); private: - float _scale = 1.0; + real_t _scale = 1.0; Ref<Texture2D> texture; Vector2 texture_offset; @@ -165,10 +166,10 @@ public: void set_texture_offset(const Vector2 &p_offset); Vector2 get_texture_offset() const; - void set_texture_scale(float p_scale); - float get_texture_scale() const; + void set_texture_scale(real_t p_scale); + real_t get_texture_scale() const; - String get_configuration_warning() const override; + TypedArray<String> get_configuration_warnings() const override; PointLight2D(); }; @@ -176,14 +177,14 @@ public: class DirectionalLight2D : public Light2D { GDCLASS(DirectionalLight2D, Light2D); - float max_distance = 10000.0; + real_t max_distance = 10000.0; protected: static void _bind_methods(); public: - void set_max_distance(float p_distance); - float get_max_distance() const; + void set_max_distance(real_t p_distance); + real_t get_max_distance() const; DirectionalLight2D(); }; diff --git a/scene/2d/light_occluder_2d.cpp b/scene/2d/light_occluder_2d.cpp index 9589702e2e..fdc28f81c2 100644 --- a/scene/2d/light_occluder_2d.cpp +++ b/scene/2d/light_occluder_2d.cpp @@ -242,24 +242,18 @@ int LightOccluder2D::get_occluder_light_mask() const { return mask; } -String LightOccluder2D::get_configuration_warning() const { - String warning = Node2D::get_configuration_warning(); +TypedArray<String> LightOccluder2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); if (!occluder_polygon.is_valid()) { - if (!warning.is_empty()) { - warning += "\n\n"; - } - warning += 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) { - if (!warning.is_empty()) { - warning += "\n\n"; - } - warning += 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 warning; + return warnings; } void LightOccluder2D::set_as_sdf_collision(bool p_enable) { diff --git a/scene/2d/light_occluder_2d.h b/scene/2d/light_occluder_2d.h index f567c6d965..b4a48d1062 100644 --- a/scene/2d/light_occluder_2d.h +++ b/scene/2d/light_occluder_2d.h @@ -106,7 +106,7 @@ public: void set_as_sdf_collision(bool p_enable); bool is_set_as_sdf_collision() const; - String get_configuration_warning() const override; + TypedArray<String> get_configuration_warnings() const override; LightOccluder2D(); ~LightOccluder2D(); diff --git a/scene/2d/line_builder.cpp b/scene/2d/line_builder.cpp index 892ccadfda..a8a2639ccf 100644 --- a/scene/2d/line_builder.cpp +++ b/scene/2d/line_builder.cpp @@ -62,14 +62,6 @@ static SegmentIntersectionResult segment_intersection( return SEGMENT_PARALLEL; } -// TODO I'm pretty sure there is an even faster way to swap things -template <typename T> -static inline void swap(T &a, T &b) { - T tmp = a; - a = b; - b = tmp; -} - static float calculate_total_distance(const Vector<Vector2> &points) { float d = 0.f; for (int i = 1; i < points.size(); ++i) { @@ -375,7 +367,7 @@ void LineBuilder::build() { } if (intersection_result != SEGMENT_INTERSECT) { - // In this case the joint is too corrputed to be re-used, + // 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); } @@ -485,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]]); } @@ -520,7 +512,7 @@ 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); } @@ -569,7 +561,7 @@ 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); diff --git a/scene/2d/line_builder.h b/scene/2d/line_builder.h index 654e61422b..16c88d00e9 100644 --- a/scene/2d/line_builder.h +++ b/scene/2d/line_builder.h @@ -31,10 +31,7 @@ #ifndef LINE_BUILDER_H #define LINE_BUILDER_H -#include "core/math/color.h" -#include "core/math/vector2.h" #include "line_2d.h" -#include "scene/resources/gradient.h" class LineBuilder { public: diff --git a/scene/2d/y_sort.cpp b/scene/2d/listener_2d.cpp index 7e7bc27cc2..444f05f2b1 100644 --- a/scene/2d/y_sort.cpp +++ b/scene/2d/listener_2d.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* y_sort.cpp */ +/* listener_2d.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,25 +28,85 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "y_sort.h" +#include "listener_2d.h" -void YSort::set_sort_enabled(bool p_enabled) { - sort_enabled = p_enabled; - RS::get_singleton()->canvas_item_set_sort_children_by_y(get_canvas_item(), sort_enabled); +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 YSort::is_sort_enabled() const { - return sort_enabled; +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 YSort::_bind_methods() { - 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); +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); +} - ADD_GROUP("Sort", "sort_"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sort_enabled"), "set_sort_enabled", "is_sort_enabled"); +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; } -YSort::YSort() { - RS::get_singleton()->canvas_item_set_sort_children_by_y(get_canvas_item(), true); +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.h b/scene/2d/listener_2d.h index 7d36ee3391..0289a8087d 100644 --- a/scene/2d/y_sort.h +++ b/scene/2d/listener_2d.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* y_sort.h */ +/* listener_2d.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,20 +28,34 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef Y_SORT_H -#define Y_SORT_H +#ifndef LISTENER_2D_H +#define LISTENER_2D_H #include "scene/2d/node_2d.h" +#include "scene/main/window.h" + +class Listener2D : public Node2D { + GDCLASS(Listener2D, Node2D); + +private: + bool current = false; + + friend class Viewport; + +protected: + void _update_listener(); + + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + void _notification(int p_what); -class YSort : public Node2D { - GDCLASS(YSort, Node2D); - bool sort_enabled = true; static void _bind_methods(); public: - void set_sort_enabled(bool p_enabled); - bool is_sort_enabled() const; - YSort(); + void make_current(); + void clear_current(); + bool is_current() const; }; -#endif // Y_SORT_H +#endif diff --git a/scene/2d/mesh_instance_2d.cpp b/scene/2d/mesh_instance_2d.cpp index b7a0028199..15008390b7 100644 --- a/scene/2d/mesh_instance_2d.cpp +++ b/scene/2d/mesh_instance_2d.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "mesh_instance_2d.h" +#include "scene/scene_string_names.h" void MeshInstance2D::_notification(int p_what) { if (p_what == NOTIFICATION_DRAW) { @@ -70,7 +71,7 @@ void MeshInstance2D::set_texture(const Ref<Texture2D> &p_texture) { } texture = p_texture; update(); - emit_signal("texture_changed"); + emit_signal(SceneStringNames::get_singleton()->texture_changed); } void MeshInstance2D::set_normal_map(const Ref<Texture2D> &p_texture) { diff --git a/scene/2d/multimesh_instance_2d.cpp b/scene/2d/multimesh_instance_2d.cpp index 72a899370e..1bff2f337d 100644 --- a/scene/2d/multimesh_instance_2d.cpp +++ b/scene/2d/multimesh_instance_2d.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "multimesh_instance_2d.h" +#include "scene/scene_string_names.h" void MultiMeshInstance2D::_notification(int p_what) { if (p_what == NOTIFICATION_DRAW) { @@ -70,7 +71,7 @@ void MultiMeshInstance2D::set_texture(const Ref<Texture2D> &p_texture) { } texture = p_texture; update(); - emit_signal("texture_changed"); + emit_signal(SceneStringNames::get_singleton()->texture_changed); } Ref<Texture2D> MultiMeshInstance2D::get_texture() const { diff --git a/scene/2d/navigation_2d.cpp b/scene/2d/navigation_2d.cpp deleted file mode 100644 index bec5ee7984..0000000000 --- a/scene/2d/navigation_2d.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/*************************************************************************/ -/* navigation_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 "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 534e31b1f2..2f00978123 100644 --- a/scene/2d/navigation_agent_2d.cpp +++ b/scene/2d/navigation_agent_2d.cpp @@ -30,21 +30,18 @@ #include "navigation_agent_2d.h" -#include "core/config/engine.h" #include "core/math/geometry_2d.h" -#include "scene/2d/navigation_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); 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); @@ -92,41 +89,21 @@ 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; } @@ -146,23 +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) { @@ -218,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]; } @@ -226,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 { @@ -267,27 +234,24 @@ 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 { - String warning = Node::get_configuration_warning(); +TypedArray<String> NavigationAgent2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); if (!Object::cast_to<Node2D>(get_parent())) { - if (!warning.is_empty()) { - warning += "\n\n"; - } - warning += 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 warning; + return warnings; } void NavigationAgent2D::update_navigation() { if (agent_parent == nullptr) { return; } - if (navigation == nullptr) { + if (!agent_parent->is_inside_tree()) { return; } if (update_frame_id == Engine::get_singleton()->get_physics_frames()) { @@ -296,7 +260,7 @@ void NavigationAgent2D::update_navigation() { update_frame_id = Engine::get_singleton()->get_physics_frames(); - Vector2 o = agent_parent->get_global_transform().get_origin(); + Vector2 o = agent_parent->get_global_position(); bool reload_path = false; @@ -319,10 +283,10 @@ 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) { @@ -335,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 6b7da4a5f2..052cd78a56 100644 --- a/scene/2d/navigation_agent_2d.h +++ b/scene/2d/navigation_agent_2d.h @@ -31,20 +31,19 @@ #ifndef NAVIGATION_AGENT_2D_H #define NAVIGATION_AGENT_2D_H -#include "core/templates/vector.h" #include "scene/main/node.h" class Node2D; -class Navigation2D; class NavigationAgent2D : public Node { GDCLASS(NavigationAgent2D, Node); Node2D *agent_parent = nullptr; - Navigation2D *navigation = nullptr; RID agent; + uint32_t navigable_layers = 1; + real_t target_desired_distance = 1.0; real_t radius; real_t neighbor_dist; @@ -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 override; + 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 7e1aefe5e2..0a105826c0 100644 --- a/scene/2d/navigation_obstacle_2d.cpp +++ b/scene/2d/navigation_obstacle_2d.cpp @@ -31,48 +31,30 @@ #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; } } @@ -86,73 +68,48 @@ NavigationObstacle2D::~NavigationObstacle2D() { agent = RID(); // Pointless } -void NavigationObstacle2D::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 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 { - String warning = Node::get_configuration_warning(); +TypedArray<String> NavigationObstacle2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); if (!Object::cast_to<Node2D>(get_parent())) { - if (!warning.is_empty()) { - warning += "\n\n"; - } - warning += 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 warning; + 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 421f8ca7cd..9cffc2c0c3 100644 --- a/scene/2d/navigation_obstacle_2d.h +++ b/scene/2d/navigation_obstacle_2d.h @@ -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 = nullptr; - + 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 override; + 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 b02cdf12ad..72ea6541e3 100644 --- a/scene/2d/navigation_region_2d.cpp +++ b/scene/2d/navigation_region_2d.cpp @@ -30,11 +30,9 @@ #include "navigation_region_2d.h" -#include "core/config/engine.h" #include "core/core_string_names.h" #include "core/math/geometry_2d.h" #include "core/os/mutex.h" -#include "navigation_2d.h" #include "servers/navigation_server_2d.h" #include "thirdparty/misc/polypartition.h" @@ -170,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()); @@ -365,10 +363,10 @@ void NavigationRegion2D::set_enabled(bool p_enabled) { 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()) { @@ -380,6 +378,14 @@ 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 { @@ -394,35 +400,24 @@ bool NavigationRegion2D::_edit_is_selected_on_click(const Point2 &p_point, doubl 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; } @@ -432,33 +427,47 @@ 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); - - for (int j = 2; j < polygon.size(); j++) { - int kofs[3] = { 0, j - 1, j }; - for (int k = 0; k < 3; k++) { - int idx = polygon[kofs[k]]; - ERR_FAIL_INDEX(idx, vsize); - indices.push_back(idx); - } + 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]]; } + + // Generate the polygon color, slightly randomly modified from the settings one. + Color random_variation_color; + random_variation_color.set_hsv(color.get_h() + rand.random(-1.0, 1.0) * 0.05, color.get_s(), color.get_v() + rand.random(-1.0, 1.0) * 0.1); + random_variation_color.a = color.a; + Vector<Color> colors; + colors.push_back(random_variation_color); + + RS::get_singleton()->canvas_item_add_polygon(get_canvas_item(), vertices, colors); + } + + // 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; } @@ -481,7 +490,7 @@ void NavigationRegion2D::set_navigation_polygon(const Ref<NavigationPolygon> &p_ } _navpoly_changed(); - update_configuration_warning(); + update_configuration_warnings(); } Ref<NavigationPolygon> NavigationRegion2D::get_navigation_polygon() const { @@ -493,32 +502,22 @@ void NavigationRegion2D::_navpoly_changed() { update(); } } - -String NavigationRegion2D::get_configuration_warning() const { - if (!is_visible_in_tree() || !is_inside_tree()) { - return String(); +void NavigationRegion2D::_map_changed(RID p_map) { + if (enabled && get_world_2d()->get_navigation_map() == p_map) { + update(); } +} - String warning = Node2D::get_configuration_warning(); +TypedArray<String> NavigationRegion2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node2D::get_configuration_warnings(); - if (!navpoly.is_valid()) { - if (!warning.is_empty()) { - warning += "\n\n"; + 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.")); } - warning += 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) { - if (Object::cast_to<Navigation2D>(c)) { - return warning; - } - c = Object::cast_to<Node2D>(c->get_parent()); - } - if (!warning.is_empty()) { - warning += "\n\n"; - } - return warning + TTR("NavigationRegion2D must be a child or grandchild to a Navigation2D node. It only provides navigation data."); + return warnings; } void NavigationRegion2D::_bind_methods() { @@ -528,10 +527,14 @@ void NavigationRegion2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &NavigationRegion2D::set_enabled); ClassDB::bind_method(D_METHOD("is_enabled"), &NavigationRegion2D::is_enabled); + ClassDB::bind_method(D_METHOD("set_layers", "layers"), &NavigationRegion2D::set_layers); + ClassDB::bind_method(D_METHOD("get_layers"), &NavigationRegion2D::get_layers); + ClassDB::bind_method(D_METHOD("_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() { diff --git a/scene/2d/navigation_region_2d.h b/scene/2d/navigation_region_2d.h index 0b9a258a25..2db8d70791 100644 --- a/scene/2d/navigation_region_2d.h +++ b/scene/2d/navigation_region_2d.h @@ -91,17 +91,15 @@ public: ~NavigationPolygon() {} }; -class Navigation2D; - class NavigationRegion2D : public Node2D { GDCLASS(NavigationRegion2D, Node2D); bool enabled = true; RID region; - Navigation2D *navigation = nullptr; Ref<NavigationPolygon> navpoly; void _navpoly_changed(); + void _map_changed(RID p_RID); protected: void _notification(int p_what); @@ -116,10 +114,13 @@ public: void set_enabled(bool p_enabled); bool is_enabled() const; + void set_layers(uint32_t p_layers); + uint32_t get_layers() const; + void set_navigation_polygon(const Ref<NavigationPolygon> &p_navpoly); Ref<NavigationPolygon> get_navigation_polygon() const; - String get_configuration_warning() const override; + TypedArray<String> get_configuration_warnings() const override; NavigationRegion2D(); ~NavigationRegion2D(); diff --git a/scene/2d/node_2d.cpp b/scene/2d/node_2d.cpp index bf311632c8..6a8788ee6e 100644 --- a/scene/2d/node_2d.cpp +++ b/scene/2d/node_2d.cpp @@ -30,11 +30,6 @@ #include "node_2d.h" -#include "core/object/message_queue.h" -#include "scene/gui/control.h" -#include "scene/main/window.h" -#include "servers/rendering_server.h" - #ifdef TOOLS_ENABLED Dictionary Node2D::_edit_get_state() const { Dictionary state; @@ -71,12 +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(); } -float Node2D::_edit_get_rotation() const { +real_t Node2D::_edit_get_rotation() const { return angle; } @@ -148,7 +143,7 @@ void Node2D::set_position(const Point2 &p_pos) { _update_transform(); } -void Node2D::set_rotation(float p_radians) { +void Node2D::set_rotation(real_t p_radians) { if (_xform_dirty) { ((Node2D *)this)->_update_xform_values(); } @@ -156,7 +151,7 @@ void Node2D::set_rotation(float p_radians) { _update_transform(); } -void Node2D::set_skew(float p_radians) { +void Node2D::set_skew(real_t p_radians) { if (_xform_dirty) { ((Node2D *)this)->_update_xform_values(); } @@ -164,24 +159,16 @@ void Node2D::set_skew(float p_radians) { _update_transform(); } -void Node2D::set_rotation_degrees(float p_degrees) { - set_rotation(Math::deg2rad(p_degrees)); -} - -void Node2D::set_skew_degrees(float p_degrees) { - set_skew(Math::deg2rad(p_degrees)); -} - void Node2D::set_scale(const Size2 &p_scale) { 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(); @@ -194,7 +181,7 @@ Point2 Node2D::get_position() const { return pos; } -float Node2D::get_rotation() const { +real_t Node2D::get_rotation() const { if (_xform_dirty) { ((Node2D *)this)->_update_xform_values(); } @@ -202,7 +189,7 @@ float Node2D::get_rotation() const { return angle; } -float Node2D::get_skew() const { +real_t Node2D::get_skew() const { if (_xform_dirty) { ((Node2D *)this)->_update_xform_values(); } @@ -210,14 +197,6 @@ float Node2D::get_skew() const { return skew; } -float Node2D::get_rotation_degrees() const { - return Math::rad2deg(get_rotation()); -} - -float Node2D::get_skew_degrees() const { - return Math::rad2deg(get_skew()); -} - Size2 Node2D::get_scale() const { if (_xform_dirty) { ((Node2D *)this)->_update_xform_values(); @@ -230,7 +209,7 @@ 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); } @@ -246,7 +225,7 @@ 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) { @@ -255,7 +234,7 @@ void Node2D::move_x(float p_delta, bool p_scaled) { 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) { @@ -279,28 +258,20 @@ 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(); } @@ -379,7 +350,7 @@ 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(); } @@ -391,19 +362,24 @@ Point2 Node2D::to_global(Point2 p_local) const { return get_global_transform().xform(p_local); } +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_skew_degrees", "degrees"), &Node2D::set_skew_degrees); 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_skew_degrees"), &Node2D::get_skew_degrees); ClassDB::bind_method(D_METHOD("get_scale"), &Node2D::get_scale); ClassDB::bind_method(D_METHOD("rotate", "radians"), &Node2D::rotate); @@ -417,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); @@ -437,24 +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::FLOAT, "skew", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_skew", "get_skew"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "skew_degrees", PROPERTY_HINT_RANGE, "-89.9,89.9,0.1", PROPERTY_USAGE_EDITOR), "set_skew_degrees", "get_skew_degrees"); - 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"); + 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 c27d740b8a..3e66541e32 100644 --- a/scene/2d/node_2d.h +++ b/scene/2d/node_2d.h @@ -37,11 +37,12 @@ class Node2D : public CanvasItem { GDCLASS(Node2D, CanvasItem); Point2 pos; - float angle = 0.0; + real_t angle = 0.0; Size2 _scale = Vector2(1, 1); - float skew = 0.0; + real_t skew = 0.0; int z_index = 0; bool z_relative = true; + bool y_sort_enabled = false; Transform2D _mat; @@ -65,51 +66,45 @@ public: 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) override; - virtual float _edit_get_rotation() const override; + 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) override; #endif void set_position(const Point2 &p_pos); - void set_rotation(float p_radians); - void set_rotation_degrees(float p_degrees); - void set_skew(float p_radians); - void set_skew_degrees(float p_radians); + 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_skew() const; - float get_rotation_degrees() const; - float get_skew_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; @@ -117,6 +112,9 @@ 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 override; diff --git a/scene/2d/parallax_background.cpp b/scene/2d/parallax_background.cpp index c93915d1bc..4870ae614b 100644 --- a/scene/2d/parallax_background.cpp +++ b/scene/2d/parallax_background.cpp @@ -51,11 +51,11 @@ void ParallaxBackground::_camera_moved(const Transform2D &p_transform, const Poi 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; } diff --git a/scene/2d/parallax_background.h b/scene/2d/parallax_background.h index c9991efc9d..3745c5b587 100644 --- a/scene/2d/parallax_background.h +++ b/scene/2d/parallax_background.h @@ -31,15 +31,13 @@ #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 = 1.0; + real_t scale = 1.0; Point2 base_offset; Point2 base_scale = Vector2(1, 1); Point2 screen_offset; @@ -61,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 a38338e1e3..67e35cc7a3 100644 --- a/scene/2d/parallax_layer.cpp +++ b/scene/2d/parallax_layer.cpp @@ -30,7 +30,6 @@ #include "parallax_layer.h" -#include "core/config/engine.h" #include "parallax_background.h" void ParallaxLayer::set_motion_scale(const Size2 &p_scale) { @@ -39,7 +38,7 @@ void ParallaxLayer::set_motion_scale(const Size2 &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); } } @@ -54,7 +53,7 @@ void ParallaxLayer::set_motion_offset(const Size2 &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); } } @@ -101,13 +100,17 @@ void ParallaxLayer::_notification(int p_what) { _update_mirroring(); } break; case NOTIFICATION_EXIT_TREE: { + if (Engine::get_singleton()->is_editor_hint()) { + break; + } + set_position(orig_offset); set_scale(orig_scale); } break; } } -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()) { @@ -135,17 +138,14 @@ void ParallaxLayer::set_base_offset_and_scale(const Point2 &p_offset, float p_sc _update_mirroring(); } -String ParallaxLayer::get_configuration_warning() const { - String warning = Node2D::get_configuration_warning(); +TypedArray<String> ParallaxLayer::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); if (!Object::cast_to<ParallaxBackground>(get_parent())) { - if (!warning.is_empty()) { - warning += "\n\n"; - } - warning += 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 warning; + return warnings; } void ParallaxLayer::_bind_methods() { diff --git a/scene/2d/parallax_layer.h b/scene/2d/parallax_layer.h index 86694c7724..cc2d2e096e 100644 --- a/scene/2d/parallax_layer.h +++ b/scene/2d/parallax_layer.h @@ -59,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 override; + TypedArray<String> get_configuration_warnings() const override; ParallaxLayer(); }; diff --git a/scene/2d/path_2d.cpp b/scene/2d/path_2d.cpp index 724998641f..ed30e871d7 100644 --- a/scene/2d/path_2d.cpp +++ b/scene/2d/path_2d.cpp @@ -30,9 +30,7 @@ #include "path_2d.h" -#include "core/config/engine.h" #include "core/math/geometry_2d.h" -#include "scene/scene_string_names.h" #ifdef TOOLS_ENABLED #include "editor/editor_scale.h" @@ -96,9 +94,9 @@ 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(0.5, 0.6, 1.0, 0.7); @@ -164,14 +162,14 @@ void PathFollow2D::_update_transform() { 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 (rotates) { - float ahead = offset + lookahead; + real_t ahead = offset + lookahead; if (loop && ahead >= path_length) { // If our lookahead will loop, we need to check if the path is closed. @@ -240,7 +238,7 @@ bool PathFollow2D::get_cubic_interpolation() const { void PathFollow2D::_validate_property(PropertyInfo &property) const { if (property.name == "offset") { - float max = 10000.0; + real_t max = 10000.0; if (path && path->get_curve().is_valid()) { max = path->get_curve()->get_baked_length(); } @@ -249,21 +247,16 @@ void PathFollow2D::_validate_property(PropertyInfo &property) const { } } -String PathFollow2D::get_configuration_warning() const { - if (!is_visible_in_tree() || !is_inside_tree()) { - return String(); - } - - String warning = Node2D::get_configuration_warning(); +TypedArray<String> PathFollow2D::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); - if (!Object::cast_to<Path2D>(get_parent())) { - if (!warning.is_empty()) { - warning += "\n\n"; + 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.")); } - warning += TTR("PathFollow2D only works when set as a child of a Path2D node."); } - return warning; + return warnings; } void PathFollow2D::_bind_methods() { @@ -301,11 +294,11 @@ void PathFollow2D::_bind_methods() { 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); @@ -321,39 +314,39 @@ void PathFollow2D::set_offset(float p_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) { _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) { _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) { +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 { +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 { @@ -361,11 +354,11 @@ float PathFollow2D::get_unit_offset() const { } } -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; } diff --git a/scene/2d/path_2d.h b/scene/2d/path_2d.h index a748817555..3b12f025fc 100644 --- a/scene/2d/path_2d.h +++ b/scene/2d/path_2d.h @@ -81,20 +81,20 @@ protected: static void _bind_methods(); public: - void set_offset(float p_offset); - float get_offset() const; + void set_offset(real_t p_offset); + real_t get_offset() const; - void set_h_offset(float p_h_offset); - float get_h_offset() const; + void set_h_offset(real_t p_h_offset); + real_t get_h_offset() const; - void set_v_offset(float p_v_offset); - float get_v_offset() const; + void set_v_offset(real_t p_v_offset); + real_t get_v_offset() const; - void set_unit_offset(float p_unit_offset); - float get_unit_offset() const; + void set_unit_offset(real_t p_unit_offset); + real_t get_unit_offset() const; - void set_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; @@ -105,7 +105,7 @@ public: void set_cubic_interpolation(bool p_enable); bool get_cubic_interpolation() const; - String get_configuration_warning() const override; + TypedArray<String> get_configuration_warnings() const override; PathFollow2D() {} }; 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 12847e52ac..46a2772bad 100644 --- a/scene/2d/navigation_2d.h +++ b/scene/2d/physical_bone_2d.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* navigation_2d.h */ +/* physical_bone_2d.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,44 +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 { - GDCLASS(Navigation2D, Node2D); +#include "scene/2d/skeleton_2d.h" - 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 96d8fb609b..30f012c7aa 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -30,96 +30,116 @@ #include "physics_body_2d.h" -#include "core/config/engine.h" #include "core/core_string_names.h" -#include "core/math/math_funcs.h" -#include "core/object/class_db.h" -#include "core/templates/list.h" -#include "core/templates/rid.h" #include "scene/scene_string_names.h" -void PhysicsBody2D::_notification(int p_what) { -} - 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("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_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) { - collision_mask = p_mask; - PhysicsServer2D::get_singleton()->body_set_collision_mask(get_rid(), p_mask); -} +Ref<KinematicCollision2D> PhysicsBody2D::_move(const Vector2 &p_motion, bool p_test_only, real_t p_margin) { + PhysicsServer2D::MotionResult result; -uint32_t PhysicsBody2D::get_collision_mask() const { - return collision_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; + } -void PhysicsBody2D::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); + motion_cache->result = result; + return motion_cache; } - set_collision_mask(mask); -} -bool PhysicsBody2D::get_collision_mask_bit(int p_bit) const { - return get_collision_mask() & (1 << p_bit); + return Ref<KinematicCollision2D>(); } -void PhysicsBody2D::set_collision_layer_bit(int p_bit, bool p_value) { - uint32_t collision_layer = get_collision_layer(); - if (p_value) { - collision_layer |= 1 << p_bit; - } else { - collision_layer &= ~(1 << p_bit); +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; + } + } + + if (p_cancel_sliding) { + // When motion is null, recovery is the resulting motion. + Vector2 motion_normal; + if (motion_length > CMP_EPSILON) { + motion_normal = p_motion / motion_length; + } + + // Check depth of recovery. + 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; + } + } + } + + if (!p_test_only) { + gt.elements[2] += r_result.travel; + set_global_transform(gt); } - set_collision_layer(collision_layer); -} -bool PhysicsBody2D::get_collision_layer_bit(int p_bit) const { - return get_collision_layer() & (1 << p_bit); + return colliding; } -PhysicsBody2D::PhysicsBody2D(PhysicsServer2D::BodyMode p_mode) : - CollisionObject2D(PhysicsServer2D::get_singleton()->body_create(), false) { - PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), p_mode); - set_pickable(false); +bool PhysicsBody2D::test_move(const Transform2D &p_from, const Vector2 &p_motion, const Ref<KinematicCollision2D> &r_collision, real_t p_margin) { + ERR_FAIL_COND_V(!is_inside_tree(), false); + + PhysicsServer2D::MotionResult *r = nullptr; + 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); + } + + return PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), p_from, p_motion, p_margin, r); } TypedArray<PhysicsBody2D> PhysicsBody2D::get_collision_exceptions() { List<RID> exceptions; PhysicsServer2D::get_singleton()->body_get_collision_exceptions(get_rid(), &exceptions); Array ret; - for (List<RID>::Element *E = exceptions.front(); E; E = E->next()) { - RID body = E->get(); + for (const RID &body : exceptions) { ObjectID instance_id = PhysicsServer2D::get_singleton()->body_get_object_instance_id(body); Object *obj = ObjectDB::get_instance(instance_id); PhysicsBody2D *physics_body = Object::cast_to<PhysicsBody2D>(obj); @@ -144,11 +164,13 @@ void PhysicsBody2D::remove_collision_exception_with(Node *p_node) { void StaticBody2D::set_constant_linear_velocity(const Vector2 &p_vel) { constant_linear_velocity = p_vel; + 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); } @@ -188,16 +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) { } -StaticBody2D::~StaticBody2D() { +StaticBody2D::StaticBody2D(PhysicsServer2D::BodyMode p_mode) : + PhysicsBody2D(p_mode) { } void StaticBody2D::_reload_physics_characteristics() { @@ -210,6 +229,86 @@ void StaticBody2D::_reload_physics_characteristics() { } } +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); @@ -226,7 +325,7 @@ 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; @@ -247,13 +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; @@ -268,6 +367,7 @@ 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) { @@ -286,7 +386,7 @@ void RigidBody2D::_body_inout(int p_status, ObjectID p_instance, int p_body_shap } 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 { @@ -310,45 +410,39 @@ void RigidBody2D::_body_inout(int p_status, ObjectID p_instance, int p_body_shap 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 = 0; int local_shape = 0; }; -bool RigidBody2D::_test_motion(const Vector2 &p_motion, bool p_infinite_inertia, real_t 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) { -#ifdef DEBUG_ENABLED - state = Object::cast_to<PhysicsDirectBodyState2D>(p_state); -#else - state = (PhysicsDirectBodyState2D *)p_state; //trust it -#endif - +void RigidBody2D::_body_state_changed(PhysicsDirectBodyState2D *p_state) { set_block_transform_notify(true); // don't want notify (would feedback loop) if (mode != MODE_KINEMATIC) { - set_global_transform(state->get_transform()); + set_global_transform(p_state->get_transform()); } - linear_velocity = state->get_linear_velocity(); - angular_velocity = state->get_angular_velocity(); - if (sleeping != state->is_sleeping()) { - sleeping = state->is_sleeping(); + + linear_velocity = p_state->get_linear_velocity(); + angular_velocity = p_state->get_angular_velocity(); + + if (sleeping != p_state->is_sleeping()) { + sleeping = p_state->is_sleeping(); emit_signal(SceneStringNames::get_singleton()->sleeping_state_changed); } - if (get_script_instance()) { - get_script_instance()->call("_integrate_forces", state); - } + + GDVIRTUAL_CALL(_integrate_forces, p_state); + set_block_transform_notify(false); // want it back if (contact_monitor) { @@ -363,22 +457,22 @@ void RigidBody2D::_direct_state_changed(Object *p_state) { } } - _RigidBody2DInOut *toadd = (_RigidBody2DInOut *)alloca(state->get_contact_count() * sizeof(_RigidBody2DInOut)); + _RigidBody2DInOut *toadd = (_RigidBody2DInOut *)alloca(p_state->get_contact_count() * sizeof(_RigidBody2DInOut)); int toadd_count = 0; //state->get_contact_count(); RigidBody2D_RemoveAction *toremove = (RigidBody2D_RemoveAction *)alloca(rc * sizeof(RigidBody2D_RemoveAction)); int toremove_count = 0; //put the ones to add - for (int i = 0; i < state->get_contact_count(); i++) { - 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; @@ -389,6 +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; @@ -404,6 +499,7 @@ void RigidBody2D::_direct_state_changed(Object *p_state) { 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++; @@ -411,40 +507,38 @@ 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; } @@ -466,11 +560,53 @@ real_t RigidBody2D::get_mass() const { void RigidBody2D::set_inertia(real_t p_inertia) { ERR_FAIL_COND(p_inertia < 0); - PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_INERTIA, p_inertia); + inertia = p_inertia; + PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_INERTIA, inertia); } real_t RigidBody2D::get_inertia() const { - return PhysicsServer2D::get_singleton()->body_get_param(get_rid(), PhysicsServer2D::BODY_PARAM_INERTIA); + return inertia; +} + +void RigidBody2D::set_center_of_mass_mode(CenterOfMassMode p_mode) { + if (center_of_mass_mode == p_mode) { + return; + } + + center_of_mass_mode = p_mode; + + switch (center_of_mass_mode) { + case CENTER_OF_MASS_MODE_AUTO: { + center_of_mass = Vector2(); + PhysicsServer2D::get_singleton()->body_reset_mass_properties(get_rid()); + if (inertia != 0.0) { + PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_INERTIA, inertia); + } + } break; + + case CENTER_OF_MASS_MODE_CUSTOM: { + PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_CENTER_OF_MASS, center_of_mass); + } break; + } +} + +RigidBody2D::CenterOfMassMode RigidBody2D::get_center_of_mass_mode() const { + return center_of_mass_mode; +} + +void RigidBody2D::set_center_of_mass(const Vector2 &p_center_of_mass) { + if (center_of_mass == p_center_of_mass) { + return; + } + + ERR_FAIL_COND(center_of_mass_mode != CENTER_OF_MASS_MODE_CUSTOM); + center_of_mass = p_center_of_mass; + + PhysicsServer2D::get_singleton()->body_set_param(get_rid(), PhysicsServer2D::BODY_PARAM_CENTER_OF_MASS, center_of_mass); +} + +const Vector2 &RigidBody2D::get_center_of_mass() const { + return center_of_mass; } void RigidBody2D::set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override) { @@ -522,25 +658,15 @@ real_t RigidBody2D::get_angular_damp() const { } void RigidBody2D::set_axis_velocity(const Vector2 &p_axis) { - Vector2 v = state ? state->get_linear_velocity() : linear_velocity; Vector2 axis = p_axis.normalized(); - v -= axis * axis.dot(v); - v += p_axis; - if (state) { - set_linear_velocity(v); - } else { - PhysicsServer2D::get_singleton()->body_set_axis_velocity(get_rid(), p_axis); - linear_velocity = v; - } + linear_velocity -= axis * axis.dot(linear_velocity); + linear_velocity += p_axis; + PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, linear_velocity); } void RigidBody2D::set_linear_velocity(const Vector2 &p_velocity) { linear_velocity = p_velocity; - if (state) { - state->set_linear_velocity(linear_velocity); - } else { - PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, linear_velocity); - } + PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, linear_velocity); } Vector2 RigidBody2D::get_linear_velocity() const { @@ -549,11 +675,7 @@ Vector2 RigidBody2D::get_linear_velocity() const { void RigidBody2D::set_angular_velocity(real_t p_velocity) { angular_velocity = p_velocity; - if (state) { - state->set_angular_velocity(angular_velocity); - } else { - PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, angular_velocity); - } + PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, angular_velocity); } real_t RigidBody2D::get_angular_velocity() const { @@ -700,34 +822,32 @@ bool RigidBody2D::is_contact_monitor_enabled() const { 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.is_empty()) { - 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() { @@ -740,6 +860,12 @@ void RigidBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_inertia"), &RigidBody2D::get_inertia); ClassDB::bind_method(D_METHOD("set_inertia", "inertia"), &RigidBody2D::set_inertia); + ClassDB::bind_method(D_METHOD("set_center_of_mass_mode", "mode"), &RigidBody2D::set_center_of_mass_mode); + ClassDB::bind_method(D_METHOD("get_center_of_mass_mode"), &RigidBody2D::get_center_of_mass_mode); + + ClassDB::bind_method(D_METHOD("set_center_of_mass", "center_of_mass"), &RigidBody2D::set_center_of_mass); + ClassDB::bind_method(D_METHOD("get_center_of_mass"), &RigidBody2D::get_center_of_mass); + ClassDB::bind_method(D_METHOD("set_physics_material_override", "physics_material_override"), &RigidBody2D::set_physics_material_override); ClassDB::bind_method(D_METHOD("get_physics_material_override"), &RigidBody2D::get_physics_material_override); @@ -791,17 +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::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"); @@ -820,25 +945,36 @@ 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); } +void RigidBody2D::_validate_property(PropertyInfo &property) const { + if (center_of_mass_mode != CENTER_OF_MASS_MODE_CUSTOM) { + if (property.name == "center_of_mass") { + property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL; + } + } +} + RigidBody2D::RigidBody2D() : - PhysicsBody2D(PhysicsServer2D::BODY_MODE_RIGID) { - PhysicsServer2D::get_singleton()->body_set_force_integration_callback(get_rid(), this, "_direct_state_changed"); + PhysicsBody2D(PhysicsServer2D::BODY_MODE_DYNAMIC) { + PhysicsServer2D::get_singleton()->body_set_state_sync_callback(get_rid(), this, _body_state_changed_callback); } RigidBody2D::~RigidBody2D() { @@ -859,385 +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) { - Collision col; +// So, if you pass 45 as limit, avoid numerical precision errors when angle is 45. +#define FLOOR_ANGLE_THRESHOLD 0.01 - 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; - } +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(); - 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) { - PhysicsServer2D::SeparationResult sep_res[8]; //max 8 rays + motion_results.clear(); - 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; - real_t deepest_depth; - for (int i = 0; i < hits; i++) { - if (deepest == -1 || sep_res[i].collision_depth > deepest_depth) { - deepest = i; - deepest_depth = sep_res[i].collision_depth; + if (!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; - } -} - -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; + _move_and_slide_free(delta); } - if (!p_test_only) { - gt.elements[2] += result.motion; - set_global_transform(gt); + if (!on_floor && !on_wall) { + // Add last platform velocity when just left a moving platform. + linear_velocity += current_platform_velocity; } - 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 - -Vector2 KinematicBody2D::move_and_slide(const Vector2 &p_linear_velocity, const Vector2 &p_up_direction, bool p_stop_on_slope, int p_max_slides, real_t p_floor_max_angle, bool p_infinite_inertia) { - Vector2 body_velocity = p_linear_velocity; - Vector2 body_velocity_normal = body_velocity.normalized(); - Vector2 up_direction = p_up_direction.normalized(); - - 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(); - } - } +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); - // 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()); + Vector2 prev_floor_normal = floor_normal; + RID prev_platform_rid = platform_rid; + int prev_platform_layer = platform_layer; - on_floor = false; - on_floor_body = RID(); - on_ceiling = false; - on_wall = false; - colliders.clear(); + platform_rid = RID(); floor_normal = Vector2(); - floor_velocity = Vector2(); - - while (p_max_slides) { - Collision collision; - bool found_collision = false; - - 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(); + platform_velocity = Vector2(); + + // No sliding on first attempt to keep floor motion stable when possible, + // When stop on slope is enabled or when there is no up direction. + bool sliding_enabled = !floor_stop_on_slope; + // Constant speed can be applied only the first time sliding is enabled. + bool can_apply_constant_speed = sliding_enabled; + bool first_slide = true; + bool vel_dir_facing_up = linear_velocity.dot(up_direction) > 0; + Vector2 last_travel; + + for (int iteration = 0; iteration < max_slides; ++iteration) { + PhysicsServer2D::MotionResult result; + + Vector2 prev_position = get_global_transform().elements[2]; + + bool collided = move_and_collide(motion, result, margin, false, !sliding_enabled); + + if (collided) { + motion_results.push_back(result); + _set_collision_direction(result); + + if (on_floor && floor_stop_on_slope && (linear_velocity.normalized() + up_direction).length() < 0.01) { + 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 (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(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 + up_direction).length() < 0.01 && collision.travel.length() < 1) { - Transform2D gt = get_global_transform(); - gt.elements[2] -= collision.travel.slide(up_direction); - set_global_transform(gt); - return Vector2(); - } - } - } else if (Math::acos(collision.normal.dot(-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); + + Vector2 motion_slide_norm = motion.slide(prev_floor_normal).normalized(); + motion = motion_slide_norm * (motion_slide_up.length()); + collided = true; } - if (!found_collision || motion == Vector2()) { + can_apply_constant_speed = !can_apply_constant_speed && !sliding_enabled; + sliding_enabled = true; + first_slide = false; + + if (!collided || motion.is_equal_approx(Vector2())) { break; } - - --p_max_slides; } - 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, real_t p_floor_max_angle, bool p_infinite_inertia) { - Vector2 up_direction = p_up_direction.normalized(); - bool was_on_floor = on_floor; +void CharacterBody2D::_move_and_slide_free(double p_delta) { + Vector2 motion = linear_velocity * p_delta; + + platform_rid = RID(); + floor_normal = Vector2(); + platform_velocity = Vector2(); + + bool first_slide = true; + for (int iteration = 0; iteration < max_slides; ++iteration) { + PhysicsServer2D::MotionResult result; + + bool collided = move_and_collide(motion, result, margin, false, false); - Vector2 ret = move_and_slide(p_linear_velocity, 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; + 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 (up_direction != Vector2()) { - if (Math::acos(col.normal.dot(up_direction)) <= p_floor_max_angle + FLOOR_ANGLE_THRESHOLD) { - 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 = up_direction * 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 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; + } + + 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 false; +} + +void CharacterBody2D::_set_collision_direction(const PhysicsServer2D::MotionResult &p_result) { + if (motion_mode == MOTION_MODE_GROUNDED && p_result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor + on_floor = true; + floor_normal = p_result.collision_normal; + _set_platform_data(p_result); + } else if (motion_mode == MOTION_MODE_GROUNDED && p_result.get_angle(-up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling + on_ceiling = true; + } else { + 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); + } + } } -bool KinematicBody2D::is_on_floor() const { +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(); + } +} + +const Vector2 &CharacterBody2D::get_linear_velocity() const { + return linear_velocity; +} + +void CharacterBody2D::set_linear_velocity(const Vector2 &p_velocity) { + linear_velocity = p_velocity; +} + +bool CharacterBody2D::is_on_floor() const { return on_floor; } -bool KinematicBody2D::is_on_wall() const { - return on_wall; +bool CharacterBody2D::is_on_floor_only() const { + return on_floor && !on_wall && !on_ceiling; } -bool KinematicBody2D::is_on_ceiling() const { - return on_ceiling; +bool CharacterBody2D::is_on_wall() const { + return on_wall; } -Vector2 KinematicBody2D::get_floor_normal() const { - return floor_normal; +bool CharacterBody2D::is_on_wall_only() const { + return on_wall && !on_floor && !on_ceiling; } -Vector2 KinematicBody2D::get_floor_velocity() const { - return floor_velocity; +bool CharacterBody2D::is_on_ceiling() const { + return on_ceiling; } -bool KinematicBody2D::test_move(const Transform2D &p_from, const Vector2 &p_motion, bool p_infinite_inertia) { - ERR_FAIL_COND_V(!is_inside_tree(), false); +bool CharacterBody2D::is_on_ceiling_only() const { + return on_ceiling && !on_floor && !on_wall; +} - return PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), p_from, p_motion, p_infinite_inertia, margin); +Vector2 CharacterBody2D::get_floor_normal() const { + return floor_normal; } -void KinematicBody2D::set_safe_margin(real_t p_margin) { - margin = p_margin; +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)); } -real_t KinematicBody2D::get_safe_margin() const { - return margin; +Vector2 CharacterBody2D::get_platform_velocity() const { + return platform_velocity; } -int KinematicBody2D::get_slide_count() const { - return colliders.size(); +int CharacterBody2D::get_slide_collision_count() const { + return motion_results.size(); } -KinematicBody2D::Collision KinematicBody2D::get_slide_collision(int p_bounce) const { - ERR_FAIL_INDEX_V(p_bounce, colliders.size(), Collision()); - return colliders[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]; } -Ref<KinematicCollision2D> KinematicBody2D::_get_slide_collision(int 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) { - if (!sync_to_physics) { - return; - } +void CharacterBody2D::set_floor_stop_on_slope_enabled(bool p_enabled) { + floor_stop_on_slope = p_enabled; +} - PhysicsDirectBodyState2D *state = Object::cast_to<PhysicsDirectBodyState2D>(p_state); +bool CharacterBody2D::is_floor_constant_speed_enabled() const { + return floor_constant_speed; +} - last_valid_transform = state->get_transform(); - set_notify_local_transform(false); - set_global_transform(last_valid_transform); - set_notify_local_transform(true); +void CharacterBody2D::set_floor_constant_speed_enabled(bool p_enabled) { + floor_constant_speed = p_enabled; +} + +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; +} + +uint32_t CharacterBody2D::get_moving_platform_floor_layers() const { + return moving_platform_floor_layers; +} + +void CharacterBody2D::set_moving_platform_floor_layers(uint32_t p_exclude_layers) { + moving_platform_floor_layers = p_exclude_layers; } -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((real_t)45.0)), 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((real_t)45.0)), DEFVAL(true)); +uint32_t CharacterBody2D::get_moving_platform_wall_layers() const { + return moving_platform_wall_layers; +} - ClassDB::bind_method(D_METHOD("test_move", "from", "rel_vec", "infinite_inertia"), &KinematicBody2D::test_move, DEFVAL(true)); +void CharacterBody2D::set_moving_platform_wall_layers(uint32_t p_exclude_layers) { + moving_platform_wall_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); +void CharacterBody2D::set_motion_mode(MotionMode p_mode) { + motion_mode = p_mode; +} - 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); +CharacterBody2D::MotionMode CharacterBody2D::get_motion_mode() const { + return motion_mode; +} - 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); +int CharacterBody2D::get_max_slides() const { + return max_slides; +} - 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); +void CharacterBody2D::set_max_slides(int p_max_slides) { + ERR_FAIL_COND(p_max_slides < 1); + max_slides = p_max_slides; +} - ClassDB::bind_method(D_METHOD("_direct_state_changed"), &KinematicBody2D::_direct_state_changed); +real_t CharacterBody2D::get_floor_max_angle() const { + return floor_max_angle; +} - 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_floor_max_angle(real_t p_radians) { + floor_max_angle = p_radians; } -KinematicBody2D::KinematicBody2D() : - PhysicsBody2D(PhysicsServer2D::BODY_MODE_KINEMATIC) { - margin = 0.08; +real_t CharacterBody2D::get_floor_snap_length() { + return floor_snap_length; +} - on_floor = false; - on_ceiling = false; - on_wall = false; - sync_to_physics = false; +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; } -KinematicBody2D::~KinematicBody2D() { - if (motion_cache.is_valid()) { - motion_cache->owner = nullptr; +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; @@ -1248,39 +1607,48 @@ 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); + 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 collision.collider; + return result.collider_id; +} + +RID KinematicCollision2D::get_collider_rid() const { + return result.collider; } Object *KinematicCollision2D::get_collider_shape() const { @@ -1288,7 +1656,7 @@ Object *KinematicCollision2D::get_collider_shape() const { 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); } } @@ -1297,11 +1665,11 @@ Object *KinematicCollision2D::get_collider_shape() const { } int KinematicCollision2D::get_collider_shape_index() const { - return collision.collider_shape; + return result.collider_shape; } Vector2 KinematicCollision2D::get_collider_velocity() const { - return collision.collider_vel; + return result.collider_velocity; } Variant KinematicCollision2D::get_collider_metadata() const { @@ -1313,9 +1681,11 @@ void KinematicCollision2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_normal"), &KinematicCollision2D::get_normal); ClassDB::bind_method(D_METHOD("get_travel"), &KinematicCollision2D::get_travel); ClassDB::bind_method(D_METHOD("get_remainder"), &KinematicCollision2D::get_remainder); + ClassDB::bind_method(D_METHOD("get_angle", "up_direction"), &KinematicCollision2D::get_angle, DEFVAL(Vector2(0.0, -1.0))); ClassDB::bind_method(D_METHOD("get_local_shape"), &KinematicCollision2D::get_local_shape); ClassDB::bind_method(D_METHOD("get_collider"), &KinematicCollision2D::get_collider); ClassDB::bind_method(D_METHOD("get_collider_id"), &KinematicCollision2D::get_collider_id); + 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); @@ -1328,14 +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 2dc853b23b..1d6437a3ad 100644 --- a/scene/2d/physics_body_2d.h +++ b/scene/2d/physics_body_2d.h @@ -41,38 +41,29 @@ class KinematicCollision2D; class PhysicsBody2D : public CollisionObject2D { GDCLASS(PhysicsBody2D, CollisionObject2D); - uint32_t collision_layer = 1; - uint32_t collision_mask = 1; - 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); 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 = 0.0; @@ -91,24 +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 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, @@ -117,10 +137,13 @@ public: private: bool can_sleep = true; - PhysicsDirectBodyState2D *state = nullptr; - Mode mode = MODE_RIGID; + Mode mode = MODE_DYNAMIC; real_t mass = 1.0; + real_t inertia = 0.0; + CenterOfMassMode center_of_mass_mode = CENTER_OF_MASS_MODE_AUTO; + Vector2 center_of_mass; + Ref<PhysicsMaterial> physics_material_override; real_t gravity_scale = 1.0; real_t linear_damp = -1.0; @@ -155,10 +178,12 @@ private: } }; struct RigidBody2D_RemoveAction { + RID rid; ObjectID body_id; ShapePair pair; }; struct BodyState { + RID rid; //int rc; bool in_scene = false; VSet<ShapePair> shapes; @@ -173,15 +198,19 @@ private: 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, real_t 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; @@ -192,6 +221,12 @@ public: void set_inertia(real_t p_inertia); real_t get_inertia() const; + void set_center_of_mass_mode(CenterOfMassMode p_mode); + CenterOfMassMode get_center_of_mass_mode() const; + + void set_center_of_mass(const Vector2 &p_center_of_mass); + const Vector2 &get_center_of_mass() const; + void set_physics_material_override(const Ref<PhysicsMaterial> &p_physics_material_override); Ref<PhysicsMaterial> get_physics_material_override() const; @@ -246,7 +281,7 @@ public: TypedArray<Node2D> get_colliding_bodies() const; //function for script - virtual String get_configuration_warning() const override; + virtual TypedArray<String> get_configuration_warnings() const override; RigidBody2D(); ~RigidBody2D(); @@ -256,86 +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 = 0; - Variant collider_metadata; - Vector2 remainder; - Vector2 travel; - int local_shape = 0; + enum MotionMode { + MOTION_MODE_GROUNDED, + MOTION_MODE_FREE, }; + bool move_and_slide(); + + const Vector2 &get_linear_velocity() const; + void set_linear_velocity(const Vector2 &p_velocity); + + bool is_on_floor() const; + bool is_on_floor_only() const; + bool is_on_wall() const; + bool is_on_wall_only() const; + bool is_on_ceiling() const; + bool is_on_ceiling_only() const; + Vector2 get_floor_normal() const; + real_t get_floor_angle(const Vector2 &p_up_direction = Vector2(0.0, -1.0)) const; + Vector2 get_platform_velocity() const; + + int get_slide_collision_count() const; + PhysicsServer2D::MotionResult get_slide_collision(int p_bounce) const; + + CharacterBody2D(); + ~CharacterBody2D(); private: - real_t margin; + 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(real_t p_margin); - real_t 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, real_t p_floor_max_angle = Math::deg2rad((real_t)45.0), 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, real_t p_floor_max_angle = Math::deg2rad((real_t)45.0), 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); - KinematicBody2D(); - ~KinematicBody2D(); + 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); + +protected: + void _notification(int p_what); + static void _bind_methods(); + virtual void _validate_property(PropertyInfo &property) const override; }; -class KinematicCollision2D : public Reference { - GDCLASS(KinematicCollision2D, Reference); +VARIANT_ENUM_CAST(CharacterBody2D::MotionMode); - KinematicBody2D *owner; - friend class KinematicBody2D; - KinematicBody2D::Collision collision; +class KinematicCollision2D : public RefCounted { + GDCLASS(KinematicCollision2D, RefCounted); + + PhysicsBody2D *owner = nullptr; + friend class PhysicsBody2D; + friend class CharacterBody2D; + PhysicsServer2D::MotionResult result; protected: static void _bind_methods(); @@ -345,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 a60a32f1d2..7366be5a7d 100644 --- a/scene/2d/polygon_2d.cpp +++ b/scene/2d/polygon_2d.cpp @@ -88,13 +88,13 @@ bool Polygon2D::_edit_is_selected_on_click(const Point2 &p_point, double p_toler } 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; } } -#endif void Polygon2D::_skeleton_bone_setup_changed() { update(); @@ -160,8 +160,8 @@ void Polygon2D::_notification(int p_what) { if (invert) { Rect2 bounds; int highest_idx = -1; - float highest_y = -1e20; - float sum = 0.0; + real_t highest_y = -1e20; + real_t sum = 0.0; for (int i = 0; i < len; i++) { if (i == 0) { @@ -279,7 +279,7 @@ void Polygon2D::_notification(int p_what) { //normalize the weights for (int i = 0; i < vc; i++) { - float tw = 0.0; + real_t tw = 0.0; for (int j = 0; j < 4; j++) { tw += weightsw[i * 4 + j]; } @@ -302,17 +302,18 @@ 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 = Geometry2D::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); - } + 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(); @@ -333,18 +334,38 @@ void Polygon2D::_notification(int p_what) { 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 (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 (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 (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; @@ -432,23 +453,15 @@ 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(); @@ -477,12 +490,12 @@ 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; } @@ -592,9 +605,6 @@ void Polygon2D::_bind_methods() { 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); @@ -636,8 +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_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"); @@ -655,4 +664,9 @@ void Polygon2D::_bind_methods() { } Polygon2D::Polygon2D() { + mesh = RS::get_singleton()->mesh_create(); +} + +Polygon2D::~Polygon2D() { + RS::get_singleton()->free(mesh); } diff --git a/scene/2d/polygon_2d.h b/scene/2d/polygon_2d.h index 43a66aad13..bf386b9ace 100644 --- a/scene/2d/polygon_2d.h +++ b/scene/2d/polygon_2d.h @@ -55,9 +55,9 @@ class Polygon2D : public Node2D { Size2 tex_scale = Vector2(1, 1); Vector2 tex_ofs; bool tex_tile = true; - float tex_rot = 0.0; + real_t tex_rot = 0.0; bool invert = false; - float invert_border = 100.0; + real_t invert_border = 100.0; bool antialiased = false; Vector2 offset; @@ -72,13 +72,12 @@ class Polygon2D : public Node2D { void _skeleton_bone_setup_changed(); -#ifdef TOOLS_ENABLED - void _validate_property(PropertyInfo &property) const override; -#endif + RID mesh; protected: void _notification(int p_what); static void _bind_methods(); + void _validate_property(PropertyInfo &property) const override; public: #ifdef TOOLS_ENABLED @@ -118,11 +117,8 @@ public: 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; @@ -133,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; @@ -152,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 ff7a0dbac3..4f053ff8b0 100644 --- a/scene/2d/position_2d.cpp +++ b/scene/2d/position_2d.cpp @@ -30,21 +30,49 @@ #include "position_2d.h" -#include "core/config/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)); } @@ -70,7 +98,7 @@ void Position2D::_notification(int p_what) { } } -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 { @@ -80,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 { diff --git a/scene/2d/position_2d.h b/scene/2d/position_2d.h index fcaef0e6a3..9ed622c8f6 100644 --- a/scene/2d/position_2d.h +++ b/scene/2d/position_2d.h @@ -48,8 +48,8 @@ public: 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 e7b5cb5a51..3ac2128c2e 100644 --- a/scene/2d/ray_cast_2d.cpp +++ b/scene/2d/ray_cast_2d.cpp @@ -31,9 +31,6 @@ #include "ray_cast_2d.h" #include "collision_object_2d.h" -#include "core/config/engine.h" -#include "physics_body_2d.h" -#include "servers/physics_server_2d.h" void RayCast2D::set_target_position(const Vector2 &p_point) { target_position = p_point; @@ -54,18 +51,22 @@ 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; + mask |= 1 << (p_layer_number - 1); } else { - mask &= ~(1 << p_bit); + mask &= ~(1 << (p_layer_number - 1)); } set_collision_mask(mask); } -bool RayCast2D::get_collision_mask_bit(int p_bit) const { - 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 { @@ -208,36 +209,41 @@ void RayCast2D::_update_raycast_state() { } void RayCast2D::_draw_debug_shape() { - float tsize = 6.0; - bool draw_arrow = target_position.length() >= tsize; Color draw_col = collided ? Color(1.0, 0.01, 0) : get_tree()->get_debug_collisions_color(); if (!enabled) { - float g = draw_col.get_v(); + const float g = draw_col.get_v(); draw_col.r = g; draw_col.g = g; draw_col.b = g; } - draw_line(Vector2(), target_position - Vector2(0, tsize * draw_arrow), draw_col, 1.4); - // Draw an arrow indicating where the RayCast is pointing to - if (draw_arrow) { - Transform2D xf; - xf.rotate(target_position.angle()); - xf.translate(Vector2(target_position.length() - tsize, 0)); - - Vector<Vector2> pts; - pts.push_back(xf.xform(Vector2(tsize, 0))); - pts.push_back(xf.xform(Vector2(0, 0.5 * tsize))); - pts.push_back(xf.xform(Vector2(0, -0.5 * tsize))); - - Vector<Color> cols; - for (int i = 0; i < 3; i++) { - cols.push_back(draw_col); - } + 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)); - draw_primitive(pts, cols, Vector<Vector2>()); + 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() { @@ -316,8 +322,8 @@ void RayCast2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &RayCast2D::set_collision_mask); ClassDB::bind_method(D_METHOD("get_collision_mask"), &RayCast2D::get_collision_mask); - ClassDB::bind_method(D_METHOD("set_collision_mask_bit", "bit", "value"), &RayCast2D::set_collision_mask_bit); - ClassDB::bind_method(D_METHOD("get_collision_mask_bit", "bit"), &RayCast2D::get_collision_mask_bit); + ClassDB::bind_method(D_METHOD("set_collision_mask_value", "layer_number", "value"), &RayCast2D::set_collision_mask_value); + ClassDB::bind_method(D_METHOD("get_collision_mask_value", "layer_number"), &RayCast2D::get_collision_mask_value); ClassDB::bind_method(D_METHOD("set_exclude_parent_body", "mask"), &RayCast2D::set_exclude_parent_body); ClassDB::bind_method(D_METHOD("get_exclude_parent_body"), &RayCast2D::get_exclude_parent_body); diff --git a/scene/2d/ray_cast_2d.h b/scene/2d/ray_cast_2d.h index 984c6bda49..65b6e7899b 100644 --- a/scene/2d/ray_cast_2d.h +++ b/scene/2d/ray_cast_2d.h @@ -74,8 +74,8 @@ public: void set_collision_mask(uint32_t p_mask); uint32_t get_collision_mask() const; - void set_collision_mask_bit(int p_bit, bool p_value); - bool get_collision_mask_bit(int p_bit) const; + void set_collision_mask_value(int p_layer_number, bool p_value); + bool get_collision_mask_value(int p_layer_number) const; void set_exclude_parent_body(bool p_exclude_parent_body); bool get_exclude_parent_body() const; diff --git a/scene/2d/remote_transform_2d.cpp b/scene/2d/remote_transform_2d.cpp index f10714e28a..fe3e867424 100644 --- a/scene/2d/remote_transform_2d.cpp +++ b/scene/2d/remote_transform_2d.cpp @@ -29,13 +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; } @@ -138,7 +137,7 @@ void RemoteTransform2D::set_remote_node(const NodePath &p_remote_node) { _update_remote(); } - update_configuration_warning(); + update_configuration_warnings(); } NodePath RemoteTransform2D::get_remote_node() const { @@ -185,17 +184,14 @@ void RemoteTransform2D::force_update_cache() { _update_cache(); } -String RemoteTransform2D::get_configuration_warning() const { - String warning = Node2D::get_configuration_warning(); +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))) { - if (!warning.is_empty()) { - warning += "\n\n"; - } - warning += 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 warning; + return warnings; } void RemoteTransform2D::_bind_methods() { diff --git a/scene/2d/remote_transform_2d.h b/scene/2d/remote_transform_2d.h index 4a26d7b339..36fddb58c7 100644 --- a/scene/2d/remote_transform_2d.h +++ b/scene/2d/remote_transform_2d.h @@ -70,7 +70,7 @@ public: void force_update_cache(); - virtual String get_configuration_warning() const override; + TypedArray<String> get_configuration_warnings() const override; RemoteTransform2D(); }; diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp index 5728230a8c..4bbbc3575d 100644 --- a/scene/2d/skeleton_2d.cpp +++ b/scene/2d/skeleton_2d.cpp @@ -30,6 +30,67 @@ #include "skeleton_2d.h" +#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(); @@ -53,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) { @@ -77,9 +173,200 @@ 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::_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); @@ -90,8 +377,14 @@ 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) { @@ -100,7 +393,7 @@ void Bone2D::set_rest(const Transform2D &p_rest) { skeleton->_make_bone_setup_dirty(); } - update_configuration_warning(); + update_configuration_warnings(); } Transform2D Bone2D::get_rest() const { @@ -119,12 +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 { @@ -133,46 +428,145 @@ int Bone2D::get_index_in_skeleton() const { 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.is_empty()) { - 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.is_empty()) { - 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; + } +} + +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 +} - return warning; +real_t Bone2D::get_bone_angle() const { + return bone_angle; } Bone2D::Bone2D() { + skeleton = nullptr; + parent_bone = nullptr; + skeleton_index = -1; + 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 } ////////////////////////////////////// +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; +} + +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")); } } @@ -184,7 +578,7 @@ void Skeleton2D::_update_bone_setup() { bone_setup_dirty = false; 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 @@ -195,11 +589,13 @@ 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() { @@ -208,7 +604,7 @@ void Skeleton2D::_make_transform_dirty() { } transform_dirty = true; if (is_inside_tree()) { - call_deferred("_update_transform"); + call_deferred(SNAME("_update_transform")); } } @@ -263,19 +659,121 @@ void Skeleton2D::_notification(int p_what) { 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::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); @@ -285,6 +783,13 @@ 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")); } diff --git a/scene/2d/skeleton_2d.h b/scene/2d/skeleton_2d.h index 80ca8c80ac..56fd0e8504 100644 --- a/scene/2d/skeleton_2d.h +++ b/scene/2d/skeleton_2d.h @@ -32,6 +32,7 @@ #define SKELETON_2D_H #include "scene/2d/node_2d.h" +#include "scene/resources/skeleton_modification_2d.h" class Skeleton2D; @@ -46,30 +47,62 @@ class Bone2D : public Node2D { Bone2D *parent_bone = nullptr; Skeleton2D *skeleton = nullptr; Transform2D rest; - float default_length = 16.0; + + 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 override; + 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); @@ -86,6 +119,11 @@ class Skeleton2D : public Node2D { 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; @@ -100,15 +138,28 @@ class Skeleton2D : public Node2D { 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 31040020dd..5761f19a53 100644 --- a/scene/2d/sprite_2d.cpp +++ b/scene/2d/sprite_2d.cpp @@ -31,7 +31,6 @@ #include "sprite_2d.h" #include "core/core_string_names.h" -#include "core/os/os.h" #include "scene/main/window.h" #include "scene/scene_string_names.h" @@ -77,14 +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()); } @@ -129,10 +128,10 @@ void Sprite2D::_notification(int p_what) { */ 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); - texture->draw_rect_region(ci, dst_rect, src_rect, Color(1, 1, 1), false, filter_clip); + texture->draw_rect_region(ci, dst_rect, src_rect, Color(1, 1, 1), false, filter_clip_enabled); } break; } } @@ -153,7 +152,7 @@ void Sprite2D::set_texture(const Ref<Texture2D> &p_texture) { } update(); - emit_signal("texture_changed"); + emit_signal(SceneStringNames::get_singleton()->texture_changed); item_rect_changed(); } @@ -199,18 +198,18 @@ 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) { @@ -220,7 +219,7 @@ void Sprite2D::set_region_rect(const Rect2 &p_region_rect) { region_rect = p_region_rect; - if (region) { + if (region_enabled) { item_rect_changed(); } } @@ -229,13 +228,13 @@ 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) { @@ -254,15 +253,15 @@ 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) { @@ -299,8 +298,8 @@ bool Sprite2D::is_pixel_opaque(const Point2 &p_point) const { } 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)) { @@ -350,7 +349,7 @@ Rect2 Sprite2D::get_rect() const { Size2i s; - if (region) { + if (region_enabled) { s = region_rect.size; } else { s = texture->get_size(); @@ -385,7 +384,7 @@ void Sprite2D::_validate_property(PropertyInfo &property) const { property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS; } - if (!region && (property.name == "region_rect" || property.name == "region_filter_clip")) { + if (!region_enabled && (property.name == "region_rect" || property.name == "region_filter_clip")) { property.usage = PROPERTY_USAGE_NOEDITOR; } } @@ -414,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); @@ -452,12 +451,12 @@ void Sprite2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "hframes", PROPERTY_HINT_RANGE, "1,16384,1"), "set_hframes", "get_hframes"); ADD_PROPERTY(PropertyInfo(Variant::INT, "vframes", PROPERTY_HINT_RANGE, "1,16384,1"), "set_vframes", "get_vframes"); ADD_PROPERTY(PropertyInfo(Variant::INT, "frame"), "set_frame", "get_frame"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "frame_coords", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_frame_coords", "get_frame_coords"); + ADD_PROPERTY(PropertyInfo(Variant::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() { diff --git a/scene/2d/sprite_2d.h b/scene/2d/sprite_2d.h index fa765f457d..49df78c59d 100644 --- a/scene/2d/sprite_2d.h +++ b/scene/2d/sprite_2d.h @@ -39,23 +39,23 @@ class Sprite2D : public Node2D { Ref<Texture2D> texture; Color specular_color; - float shininess = 0.0; + real_t shininess = 0.0; bool centered = true; Point2 offset; bool hflip = false; bool vflip = false; - bool region = false; + bool region_enabled = false; Rect2 region_rect; - bool region_filter_clip = false; + bool region_filter_clip_enabled = false; int frame = 0; 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(); @@ -97,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); @@ -109,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; diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index d868ebae25..a139a92ab4 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -30,1157 +30,1871 @@ #include "tile_map.h" -#include "collision_object_2d.h" #include "core/io/marshalls.h" -#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 { - if (use_y_sort) { - return 1; - } else { - return quadrant_size; +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)); + + size = size.max(p_coords + Vector2i(1, 1)); + pattern[p_coords] = TileMapCell(p_source_id, p_atlas_coords, p_alternative_tile); +} + +bool TileMapPattern::has_cell(const Vector2i &p_coords) const { + return pattern.has(p_coords); +} + +void TileMapPattern::remove_cell(const Vector2i &p_coords, bool p_update_size) { + ERR_FAIL_COND(!pattern.has(p_coords)); + + 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)); + } } } -void TileMap::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - Node2D *c = this; - while (c) { - navigation = Object::cast_to<Navigation2D>(c); - if (navigation) { - break; - } +int TileMapPattern::get_cell_source_id(const Vector2i &p_coords) const { + ERR_FAIL_COND_V(!pattern.has(p_coords), TileSet::INVALID_SOURCE); - c = Object::cast_to<Node2D>(c->get_parent()); - } + return pattern[p_coords].source_id; +} - if (use_parent) { - _clear_quadrants(); - collision_parent = Object::cast_to<CollisionObject2D>(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); - 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(); + return pattern[p_coords].get_atlas_coords(); +} - } break; +int TileMapPattern::get_cell_alternative_tile(const Vector2i &p_coords) const { + ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetSource::INVALID_TILE_ALTERNATIVE); - case NOTIFICATION_EXIT_TREE: { - _update_quadrant_space(RID()); - for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { - Quadrant &q = E->get(); - if (navigation) { - for (Map<PosKey, Quadrant::NavPoly>::Element *F = q.navpoly_ids.front(); F; F = F->next()) { - NavigationServer2D::get_singleton()->region_set_map(F->get().region, RID()); - } - q.navpoly_ids.clear(); - } + return pattern[p_coords].alternative_tile; +} + +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; + } + + return a; +} + +Vector2i TileMapPattern::get_size() const { + return size; +} + +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)); + }; + } + + 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); +} - if (collision_parent) { - collision_parent->remove_shape_owner(q.shape_owner_id); - q.shape_owner_id = -1; +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); + } + } + 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); } - q.occluder_instances.clear(); } + 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: { - //move stuff - _update_quadrant_transform(); +int TileMap::get_effective_quadrant_size(int p_layer) const { + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), 1); - } break; - case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { - if (use_parent) { - _recreate_quadrants(); - } + // 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; + } +} + +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(); +} + +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; } -} -void TileMap::_update_quadrant_space(const RID &p_space) { - 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_space(q.body, p_space); - } + // 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_transform() { - if (!is_inside_tree()) { +Ref<TileSet> TileMap::get_tileset() const { + return tile_set; +} + +void TileMap::set_tileset(const Ref<TileSet> &p_tileset) { + if (p_tileset == tile_set) { return; } - Transform2D global_transform = get_global_transform(); + // Set the tileset, registering to its changes. + if (tile_set.is_valid()) { + tile_set->disconnect("changed", callable_mp(this, &TileMap::_tile_set_changed)); + } - Transform2D local_transform; - if (collision_parent) { - local_transform = get_transform(); + if (!p_tileset.is_valid()) { + _clear_internals(); } - Transform2D nav_rel; - if (navigation) { - nav_rel = get_relative_transform_to_parent(navigation); + tile_set = p_tileset; + + if (tile_set.is_valid()) { + tile_set->connect("changed", callable_mp(this, &TileMap::_tile_set_changed)); + _clear_internals(); + _recreate_internals(); } - for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { - Quadrant &q = E->get(); - Transform2D xform; - xform.set_origin(q.pos); + emit_signal(SNAME("changed")); +} - if (!use_parent) { - xform = global_transform * xform; - PhysicsServer2D::get_singleton()->body_set_state(q.body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); - } +void TileMap::set_quadrant_size(int p_size) { + ERR_FAIL_COND_MSG(p_size < 1, "TileMapQuadrant size cannot be smaller than 1."); - if (navigation) { - for (Map<PosKey, Quadrant::NavPoly>::Element *F = q.navpoly_ids.front(); F; F = F->next()) { - NavigationServer2D::get_singleton()->region_set_transform(F->get().region, nav_rel * F->get().xform); - } - } + quadrant_size = p_size; + _clear_internals(); + _recreate_internals(); + emit_signal(SNAME("changed")); +} - 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); - } +int TileMap::get_quadrant_size() const { + return quadrant_size; +} + +int TileMap::get_layers_count() const { + return layers.size(); +} + +void TileMap::add_layer(int p_to_pos) { + if (p_to_pos < 0) { + p_to_pos = layers.size(); } + + ERR_FAIL_INDEX(p_to_pos, (int)layers.size() + 1); + + // Must clear before adding the layer. + _clear_internals(); + + layers.insert(p_to_pos, TileMapLayer()); + _recreate_internals(); + notify_property_list_changed(); + + emit_signal(SNAME("changed")); + + update_configuration_warnings(); } -void TileMap::set_tileset(const Ref<TileSet> &p_tileset) { - if (tile_set.is_valid()) { - tile_set->disconnect("changed", callable_mp(this, &TileMap::_recreate_quadrants)); +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; } - _clear_quadrants(); - tile_set = p_tileset; + emit_signal(SNAME("changed")); - if (tile_set.is_valid()) { - tile_set->connect("changed", callable_mp(this, &TileMap::_recreate_quadrants)); - } else { - clear(); + update_configuration_warnings(); +} + +void TileMap::remove_layer(int p_layer) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); + + // Clear before removing the layer. + _clear_internals(); + + layers.remove(p_layer); + _recreate_internals(); + notify_property_list_changed(); + + if (selected_layer >= p_layer) { + selected_layer -= 1; } - _recreate_quadrants(); - emit_signal("settings_changed"); + emit_signal(SNAME("changed")); + + update_configuration_warnings(); } -Ref<TileSet> TileMap::get_tileset() const { - return tile_set; +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")); } -void TileMap::set_cell_size(Size2 p_size) { - ERR_FAIL_COND(p_size.x < 1 || p_size.y < 1); +String TileMap::get_layer_name(int p_layer) const { + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), String()); + return layers[p_layer].name; +} - _clear_quadrants(); - cell_size = p_size; - _recreate_quadrants(); - emit_signal("settings_changed"); +void TileMap::set_layer_enabled(int p_layer, bool p_enabled) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); + layers[p_layer].enabled = p_enabled; + _clear_internals(); + _recreate_internals(); + emit_signal(SNAME("changed")); + + update_configuration_warnings(); } -Size2 TileMap::get_cell_size() const { - return cell_size; +bool TileMap::is_layer_enabled(int p_layer) const { + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), false); + return layers[p_layer].enabled; } -void TileMap::set_quadrant_size(int p_size) { - ERR_FAIL_COND_MSG(p_size < 1, "Quadrant size cannot be smaller than 1."); +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")); - _clear_quadrants(); - quadrant_size = p_size; - _recreate_quadrants(); - emit_signal("settings_changed"); + update_configuration_warnings(); } -int TileMap::get_quadrant_size() const { - return quadrant_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; } -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; +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")); +} - if (compatibility_mode && !centered_textures) { - if (tile_origin == TILE_ORIGIN_BOTTOM_LEFT) { - offset.y += cell_size.y; - } else if (tile_origin == TILE_ORIGIN_CENTER) { - offset += cell_size / 2; - } +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; +} - 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_layer_z_index(int p_layer, int p_z_index) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); + layers[p_layer].z_index = p_z_index; + _clear_internals(); + _recreate_internals(); + emit_signal(SNAME("changed")); + + update_configuration_warnings(); +} + +int TileMap::get_layer_z_index(int p_layer) const { + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), false); + return layers[p_layer].z_index; +} + +void TileMap::set_collision_visibility_mode(TileMap::VisibilityMode p_show_collision) { + collision_visibility_mode = p_show_collision; + _clear_internals(); + _recreate_internals(); + emit_signal(SNAME("changed")); +} + +TileMap::VisibilityMode TileMap::get_collision_visibility_mode() { + return collision_visibility_mode; +} + +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")); +} + +TileMap::VisibilityMode TileMap::get_navigation_visibility_mode() { + return navigation_visibility_mode; +} + +void TileMap::set_y_sort_enabled(bool p_enable) { + Node2D::set_y_sort_enabled(p_enable); + _clear_internals(); + _recreate_internals(); + emit_signal(SNAME("changed")); +} + +Vector2i TileMap::_coords_to_quadrant_coords(int p_layer, const Vector2i &p_coords) const { + int quadrant_size = get_effective_quadrant_size(p_layer); + + // Rounding down, instead of simply rounding towards zero (truncating) + return Vector2i( + p_coords.x > 0 ? p_coords.x / quadrant_size : (p_coords.x - (quadrant_size - 1)) / quadrant_size, + p_coords.y > 0 ? p_coords.y / quadrant_size : (p_coords.y - (quadrant_size - 1)) / quadrant_size); +} + +Map<Vector2i, TileMapQuadrant>::Element *TileMap::_create_quadrant(int p_layer, const Vector2i &p_qk) { + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), nullptr); + + TileMapQuadrant q; + q.layer = p_layer; + q.coords = p_qk; + + rect_cache_dirty = true; + + // Create the debug canvas item. + RenderingServer *rs = RenderingServer::get_singleton(); + q.debug_canvas_item = rs->canvas_item_create(); + rs->canvas_item_set_z_index(q.debug_canvas_item, RS::CANVAS_ITEM_Z_MAX - 1); + rs->canvas_item_set_parent(q.debug_canvas_item, get_canvas_item()); + + // Call the create_quadrant method on plugins + if (tile_set.is_valid()) { + _rendering_create_quadrant(&q); + _physics_create_quadrant(&q); + } + + return layers[p_layer].quadrant_map.insert(p_qk, q); +} + +void TileMap::_make_quadrant_dirty(Map<Vector2i, TileMapQuadrant>::Element *Q) { + // Make the given quadrant dirty, then trigger an update later. + TileMapQuadrant &q = Q->get(); + if (!q.dirty_list_element.in_list()) { + layers[q.layer].dirty_quadrant_list.add(&q.dirty_list_element); + } + _queue_update_dirty_quadrants(); +} + +void TileMap::_make_all_quadrants_dirty() { + // Make all quandrants dirty, then trigger an update later. + for (unsigned int layer = 0; layer < layers.size(); layer++) { + for (Map<Vector2i, TileMapQuadrant>::Element *E = layers[layer].quadrant_map.front(); E; E = E->next()) { + if (!E->value().dirty_list_element.in_list()) { + layers[layer].dirty_quadrant_list.add(&E->value().dirty_list_element); } } } + _queue_update_dirty_quadrants(); +} - 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); +void TileMap::_queue_update_dirty_quadrants() { + if (pending_update || !is_inside_tree()) { + return; } + pending_update = true; + call_deferred(SNAME("_update_dirty_quadrants")); +} - 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; +void TileMap::_update_dirty_quadrants() { + if (!pending_update) { + return; + } + if (!is_inside_tree() || !tile_set.is_valid()) { + pending_update = false; + return; + } + + 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; } - } else { - offset.x = s.x - offset.x; + } + + // Call the update_dirty_quadrant method on plugins. + _rendering_update_dirty_quadrants(layers[layer].dirty_quadrant_list); + _physics_update_dirty_quadrants(layers[layer].dirty_quadrant_list); + _navigation_update_dirty_quadrants(layers[layer].dirty_quadrant_list); + _scenes_update_dirty_quadrants(layers[layer].dirty_quadrant_list); + + // Redraw the debug canvas_items. + RenderingServer *rs = RenderingServer::get_singleton(); + for (SelfList<TileMapQuadrant> *q = layers[layer].dirty_quadrant_list.first(); q; q = q->next()) { + rs->canvas_item_clear(q->self()->debug_canvas_item); + Transform2D xform; + xform.set_origin(map_to_world(q->self()->coords * get_effective_quadrant_size(layer))); + rs->canvas_item_set_transform(q->self()->debug_canvas_item, xform); + + _rendering_draw_quadrant_debug(q->self()); + _physics_draw_quadrant_debug(q->self()); + _navigation_draw_quadrant_debug(q->self()); + _scenes_draw_quadrant_debug(q->self()); + } + + // Clear the list + while (layers[layer].dirty_quadrant_list.first()) { + layers[layer].dirty_quadrant_list.remove(layers[layer].dirty_quadrant_list.first()); } } - 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; + pending_update = false; + + _recompute_rect_cache(); +} + +void TileMap::_recreate_internals() { + for (unsigned int layer = 0; layer < layers.size(); layer++) { + // Make sure that _clear_internals() was called prior. + ERR_FAIL_COND_MSG(layers[layer].quadrant_map.size() > 0, "TileMap layer " + itos(layer) + " had a non-empty quadrant map."); + + if (!layers[layer].enabled) { + continue; + } + + // Upadate the layer internals. + _rendering_update_layer(layer); + + // Recreate the quadrants. + const Map<Vector2i, TileMapCell> &tile_map = layers[layer].tile_map; + for (Map<Vector2i, TileMapCell>::Element *E = tile_map.front(); E; E = E->next()) { + Vector2i qk = _coords_to_quadrant_coords(layer, Vector2i(E->key().x, E->key().y)); + + Map<Vector2i, TileMapQuadrant>::Element *Q = layers[layer].quadrant_map.find(qk); + if (!Q) { + Q = _create_quadrant(layer, qk); + layers[layer].dirty_quadrant_list.add(&Q->get().dirty_list_element); } - } else { - offset.y = s.y - offset.y; + + Vector2i pk = E->key(); + Q->get().cells.insert(pk); + + _make_quadrant_dirty(Q); } } - if (centered_textures) { - offset += cell_size / 2 - s / 2; - } - xform.elements[2] += offset; + _update_dirty_quadrants(); } -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(); +void TileMap::_erase_quadrant(Map<Vector2i, TileMapQuadrant>::Element *Q) { + // Remove a quadrant. + TileMapQuadrant *q = &(Q->get()); + + // Call the cleanup_quadrant method on plugins. + if (tile_set.is_valid()) { + _rendering_cleanup_quadrant(q); + _physics_cleanup_quadrant(q); + _navigation_cleanup_quadrant(q); + _scenes_cleanup_quadrant(q); + } - 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); + // 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)); + } - } else if (collision_parent) { - Transform2D xform = p_xform; - xform.set_origin(xform.get_origin() + p_q.pos); + // Free the debug canvas item. + RenderingServer *rs = RenderingServer::get_singleton(); + rs->free(q->debug_canvas_item); - collision_parent->shape_owner_add_shape(p_q.shape_owner_id, p_shape); + layers[q->layer].quadrant_map.erase(Q); + rect_cache_dirty = true; +} - int real_index = collision_parent->shape_owner_get_shape_index(p_q.shape_owner_id, shape_idx); - RID rid = collision_parent->get_rid(); +void TileMap::_clear_layer_internals(int p_layer) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); - 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); - } + // Clear quadrants. + while (layers[p_layer].quadrant_map.size()) { + _erase_quadrant(layers[p_layer].quadrant_map.front()); + } + + // Clear the layers internals. + _rendering_cleanup_layer(p_layer); + + // Clear the dirty quadrants list. + while (layers[p_layer].dirty_quadrant_list.first()) { + layers[p_layer].dirty_quadrant_list.remove(layers[p_layer].dirty_quadrant_list.first()); } - shape_idx++; } -void TileMap::update_dirty_quadrants() { - if (!pending_update) { - return; +void TileMap::_clear_internals() { + // Clear quadrants. + for (unsigned int layer = 0; layer < layers.size(); layer++) { + _clear_layer_internals(layer); } - if (!is_inside_tree() || !tile_set.is_valid()) { - pending_update = false; +} + +void TileMap::_recompute_rect_cache() { + // Compute the displayed area of the tilemap. +#ifdef DEBUG_ENABLED + + if (!rect_cache_dirty) { 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); + 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 { + r_total = r_total.merge(r); + } + } } - Vector2 qofs; + rect_cache = r_total; - SceneTree *st = SceneTree::get_singleton(); - Color debug_collision_color; - Color debug_navigation_color; + item_rect_changed(); - bool debug_shapes = st && st->is_debugging_collisions_hint(); - if (debug_shapes) { - debug_collision_color = st->get_debug_collisions_color(); + rect_cache_dirty = false; +#endif +} + +/////////////////////////////// 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(); + + // Update occluders transform. + for (Map<Vector2i, Vector2i, TileMapQuadrant::CoordsWorldComparator>::Element *E_cell = q.world_to_map.front(); E_cell; E_cell = E_cell->next()) { + Transform2D xform; + xform.set_origin(E_cell->key()); + for (const RID &occluder : q.occluders) { + RS::get_singleton()->canvas_light_occluder_set_transform(occluder, get_global_transform() * xform); + } + } + } + } + } break; + case CanvasItem::NOTIFICATION_DRAW: { + if (tile_set.is_valid()) { + RenderingServer::get_singleton()->canvas_item_set_sort_children_by_y(get_canvas_item(), is_y_sort_enabled()); + } + } break; } +} - bool debug_navigation = st && st->is_debugging_navigation_hint(); - if (debug_navigation) { - debug_navigation_color = st->get_debug_navigation_color(); +void TileMap::_rendering_update_layer(int p_layer) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); + + RenderingServer *rs = RenderingServer::get_singleton(); + if (!layers[p_layer].canvas_item.is_valid()) { + RID ci = rs->canvas_item_create(); + rs->canvas_item_set_parent(ci, get_canvas_item()); + + /*Transform2D xform; + xform.set_origin(Vector2(0, p_layer)); + rs->canvas_item_set_transform(ci, xform);*/ + rs->canvas_item_set_draw_index(ci, p_layer); + + layers[p_layer].canvas_item = ci; } + RID &ci = layers[p_layer].canvas_item; + rs->canvas_item_set_sort_children_by_y(ci, layers[p_layer].y_sort_enabled); + rs->canvas_item_set_use_parent_material(ci, get_use_parent_material() || get_material().is_valid()); + rs->canvas_item_set_z_index(ci, layers[p_layer].z_index); + rs->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(get_texture_filter())); + rs->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(get_texture_repeat())); + rs->canvas_item_set_light_mask(ci, get_light_mask()); +} - while (dirty_quadrant_list.first()) { - Quadrant &q = *dirty_quadrant_list.first()->self(); +void TileMap::_rendering_cleanup_layer(int p_layer) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); - for (List<RID>::Element *E = q.canvas_items.front(); E; E = E->next()) { - vs->free(E->get()); - } + RenderingServer *rs = RenderingServer::get_singleton(); + if (!layers[p_layer].canvas_item.is_valid()) { + rs->free(layers[p_layer].canvas_item); + } +} - q.canvas_items.clear(); +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 (!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; + bool visible = is_visible_in_tree(); - 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()); - } - q.navpoly_ids.clear(); + SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); + while (q_list_element) { + TileMapQuadrant &q = *q_list_element->self(); + + RenderingServer *rs = RenderingServer::get_singleton(); + + // Free the canvas items. + for (const RID &ci : q.canvas_items) { + rs->free(ci); } + q.canvas_items.clear(); - for (Map<PosKey, Quadrant::Occluder>::Element *E = q.occluder_instances.front(); E; E = E->next()) { - RS::get_singleton()->free(E->get().id); + // Free the occluders. + for (const RID &occluder : q.occluders) { + rs->free(occluder); } - q.occluder_instances.clear(); + q.occluders.clear(); + + // Those allow to group cell per material or z-index. Ref<ShaderMaterial> prev_material; int prev_z_index = 0; RID prev_canvas_item; - RID prev_debug_canvas_item; - for (int i = 0; i < q.cells.size(); i++) { - 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); + // 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); - Vector2 wofs = _map_to_world(E->key().x, E->key().y); - Vector2 offset = wofs - q.pos + tofs; + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); - if (!tex.is_valid()) { - continue; - } + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } - Ref<ShaderMaterial> mat = tile_set->tile_get_material(c.id); - int z_index = tile_set->tile_get_z_index(c.id); + 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 (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)); - } + // --- CanvasItems --- + // Create two canvas items, for rendering and debug. + RID canvas_item; - RID canvas_item; - RID debug_canvas_item; + // Check if the material or the z_index changed. + if (prev_canvas_item == RID() || prev_material != mat || prev_z_index != z_index) { + // If so, create a new CanvasItem. + canvas_item = rs->canvas_item_create(); + if (mat.is_valid()) { + rs->canvas_item_set_material(canvas_item, mat->get_rid()); + } + rs->canvas_item_set_parent(canvas_item, layers[q.layer].canvas_item); + rs->canvas_item_set_use_parent_material(canvas_item, get_use_parent_material() || get_material().is_valid()); - if (prev_canvas_item == RID() || prev_material != mat || prev_z_index != z_index) { - 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); - - vs->canvas_item_set_default_texture_filter(canvas_item, RS::CanvasItemTextureFilter(CanvasItem::get_texture_filter())); - vs->canvas_item_set_default_texture_repeat(canvas_item, RS::CanvasItemTextureRepeat(CanvasItem::get_texture_repeat())); - - q.canvas_items.push_back(canvas_item); - - if (debug_shapes) { - 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; - } + Transform2D xform; + xform.set_origin(position); + rs->canvas_item_set_transform(canvas_item, xform); - prev_canvas_item = canvas_item; - prev_material = mat; - prev_z_index = z_index; + rs->canvas_item_set_light_mask(canvas_item, get_light_mask()); + rs->canvas_item_set_z_index(canvas_item, z_index); - } else { - canvas_item = prev_canvas_item; - if (debug_shapes) { - debug_canvas_item = prev_debug_canvas_item; - } - } + 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())); - 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); - } + q.canvas_items.push_back(canvas_item); - Size2 s; - if (r == Rect2()) { - s = tex->get_size(); - } else { - s = r.size; - } + prev_canvas_item = canvas_item; + prev_material = mat; + prev_z_index = z_index; - Rect2 rect; - rect.position = offset.floor(); - rect.size = s; - rect.size.x += fp_adjust; - rect.size.y += fp_adjust; + } else { + // Keep the same canvas_item to draw on. + canvas_item = prev_canvas_item; + } - 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; + // 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; + } } - } 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; + draw_tile(canvas_item, E_cell->key() - position, tile_set, c.source_id, c.get_atlas_coords(), c.alternative_tile, modulate); + + // --- Occluders --- + for (int i = 0; i < tile_set->get_occlusion_layers_count(); i++) { + Transform2D xform; + xform.set_origin(E_cell->key()); + if (tile_data->get_occluder(i).is_valid()) { + RID occluder_id = rs->canvas_light_occluder_create(); + rs->canvas_light_occluder_set_enabled(occluder_id, visible); + rs->canvas_light_occluder_set_transform(occluder_id, get_global_transform() * xform); + rs->canvas_light_occluder_set_polygon(occluder_id, tile_data->get_occluder(i)->get_rid()); + rs->canvas_light_occluder_attach_to_canvas(occluder_id, get_canvas()); + rs->canvas_light_occluder_set_light_mask(occluder_id, tile_set->get_occlusion_layer_light_mask(i)); + q.occluders.push_back(occluder_id); + } } } } + } - 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; - } - } else if (centered_textures) { - rect.position += cell_size / 2 - rect.size / 2; - } + _rendering_quadrant_order_dirty = true; + q_list_element = q_list_element->next(); + } - if (c.flip_h) { - rect.size.x = -rect.size.x; - tile_ofs.x = -tile_ofs.x; + // Reset the drawing indices + if (_rendering_quadrant_order_dirty) { + int index = -(int64_t)0x80000000; //always must be drawn below children. + + for (int layer = 0; layer < (int)layers.size(); layer++) { + // Sort the quadrants coords per world coordinates + Map<Vector2i, Vector2i, TileMapQuadrant::CoordsWorldComparator> world_to_map; + for (Map<Vector2i, TileMapQuadrant>::Element *E = layers[layer].quadrant_map.front(); E; E = E->next()) { + world_to_map[map_to_world(E->key())] = E->key(); } - if (c.flip_v) { - rect.size.y = -rect.size.y; - tile_ofs.y = -tile_ofs.y; + // Sort the quadrants + for (Map<Vector2i, Vector2i, TileMapQuadrant::CoordsWorldComparator>::Element *E = world_to_map.front(); E; E = E->next()) { + TileMapQuadrant &q = layers[layer].quadrant_map[E->value()]; + for (const RID &ci : q.canvas_items) { + RS::get_singleton()->canvas_item_set_draw_index(ci, index++); + } } + } + _rendering_quadrant_order_dirty = false; + } +} - if (compatibility_mode && !centered_textures) { - if (tile_origin == TILE_ORIGIN_TOP_LEFT) { - rect.position += tile_ofs; +void TileMap::_rendering_create_quadrant(TileMapQuadrant *p_quadrant) { + ERR_FAIL_COND(!tile_set.is_valid()); - } else if (tile_origin == TILE_ORIGIN_BOTTOM_LEFT) { - rect.position += tile_ofs; + _rendering_quadrant_order_dirty = true; +} - 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_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(); - } else if (tile_origin == TILE_ORIGIN_CENTER) { - rect.position += tile_ofs; + // Free the occluders. + for (const RID &occluder : p_quadrant->occluders) { + RenderingServer::get_singleton()->free(occluder); + } + p_quadrant->occluders.clear(); +} - if (c.flip_h) { - rect.position.x -= cell_size.x / 2; - } else { - rect.position.x += cell_size.x / 2; - } +void TileMap::_rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { + ERR_FAIL_COND(!tile_set.is_valid()); - if (c.flip_v) { - rect.position.y -= cell_size.y / 2; - } else { - rect.position.y += cell_size.y / 2; - } - } - } else { - rect.position += tile_ofs; + if (!Engine::get_singleton()->is_editor_hint()) { + return; + } + + // Draw a placeholder for scenes needing one. + RenderingServer *rs = RenderingServer::get_singleton(); + Vector2 quadrant_pos = map_to_world(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer)); + for (Set<Vector2i>::Element *E_cell = p_quadrant->cells.front(); E_cell; E_cell = E_cell->next()) { + const TileMapCell &c = get_cell(p_quadrant->layer, E_cell->get(), true); + + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); + + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; } - 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); - } else { - tex->draw_rect_region(canvas_item, rect, r, modulate, c.transpose, clip_uv); + 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); + } } + } + } +} - Vector<TileSet::ShapeData> shapes = tile_set->tile_get_shapes(c.id); +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)); - 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()); + 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; + } - Vector2 shape_ofs = shapes[j].shape_transform.get_origin(); + // 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; + } - _fix_cell_transform(xform, c, shape_ofs, s); + // Get tile data. + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile)); - xform *= shapes[j].shape_transform.untranslated(); + // 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); - if (debug_canvas_item.is_valid()) { - vs->canvas_item_add_set_transform(debug_canvas_item, xform); - shape->draw(debug_canvas_item, debug_collision_color); - } + // 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; - 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)); + bool transpose = tile_data->get_transpose(); + if (transpose) { + dest_rect.position = (p_position - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset); + } else { + dest_rect.position = (p_position - dest_rect.size / 2 - tile_offset); + } + + if (tile_data->get_flip_h()) { + dest_rect.size.x = -dest_rect.size.x; + } + + if (tile_data->get_flip_v()) { + dest_rect.size.y = -dest_rect.size.y; + } + + // Get the tile modulation. + Color modulate = tile_data->get_modulate(); + modulate = Color(modulate.r * p_modulation.r, modulate.g * p_modulation.g, modulate.b * p_modulation.b, modulate.a * p_modulation.a); + + // Draw the tile. + tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); + } +} + +/////////////////////////////// Physics ////////////////////////////////////// + +void TileMap::_physics_notification(int p_what) { + switch (p_what) { + case CanvasItem::NOTIFICATION_TRANSFORM_CHANGED: { + // Update the bodies transforms. + if (is_inside_tree()) { + for (int layer = 0; layer < (int)layers.size(); layer++) { + Transform2D global_transform = get_global_transform(); + + for (Map<Vector2i, TileMapQuadrant>::Element *E = layers[layer].quadrant_map.front(); E; E = E->next()) { + TileMapQuadrant &q = E->get(); + + Transform2D xform; + xform.set_origin(map_to_world(E->key() * get_effective_quadrant_size(layer))); + xform = global_transform * xform; + + for (int body_index = 0; body_index < q.bodies.size(); body_index++) { + PhysicsServer2D::get_singleton()->body_set_state(q.bodies[body_index], PhysicsServer2D::BODY_STATE_TRANSFORM, xform); } } } } + } break; + } +} - if (debug_canvas_item.is_valid()) { - vs->canvas_item_add_set_transform(debug_canvas_item, Transform2D()); - } +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()); - 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); + Transform2D global_transform = get_global_transform(); + PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); + + SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); + while (q_list_element) { + TileMapQuadrant &q = *q_list_element->self(); + + Vector2 quadrant_pos = map_to_world(q.coords * get_effective_quadrant_size(q.layer)); + + LocalVector<int> body_shape_count; + body_shape_count.resize(q.bodies.size()); + + // Clear shapes. + for (int body_index = 0; body_index < q.bodies.size(); body_index++) { + ps->body_clear_shapes(q.bodies[body_index]); + body_shape_count[body_index] = 0; + + // Position the bodies. + Transform2D xform; + xform.set_origin(quadrant_pos); + xform = global_transform * xform; + ps->body_set_state(q.bodies[body_index], PhysicsServer2D::BODY_STATE_TRANSFORM, xform); + } + + for (Set<Vector2i>::Element *E_cell = q.cells.front(); E_cell; E_cell = E_cell->next()) { + TileMapCell c = get_cell(q.layer, E_cell->get(), true); + + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); + + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; } - 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; - } - } + 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)); - Vector<int> indices; + for (int body_index = 0; body_index < q.bodies.size(); body_index++) { + int &body_shape_index = body_shape_count[body_index]; - for (int j = 0; j < navpoly->get_polygon_count(); j++) { - Vector<int> polygon = navpoly->get_polygon(j); + // 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); - for (int k = 2; k < polygon.size(); k++) { - int kofs[3] = { 0, k - 1, k }; - for (int l = 0; l < 3; l++) { - 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); + int shapes_count = tile_data->get_collision_polygon_shapes_count(body_index, polygon_index); + for (int shape_index = 0; shape_index < shapes_count; shape_index++) { + Transform2D xform = Transform2D(); + xform.set_origin(map_to_world(E_cell->get()) - quadrant_pos); + + // Add decomposed convex shapes. + Ref<ConvexPolygonShape2D> shape = tile_data->get_collision_polygon_shape(body_index, polygon_index, shape_index); + ps->body_add_shape(q.bodies[body_index], shape->get_rid(), xform); + ps->body_set_shape_metadata(q.bodies[body_index], body_shape_index, E_cell->get()); + ps->body_set_shape_as_one_way_collision(q.bodies[body_index], body_shape_index, one_way_collision, one_way_collision_margin); - vs->canvas_item_set_transform(debug_navigation_item, navxform); - vs->canvas_item_add_triangle_array(debug_navigation_item, indices, vertices, colors); + ++body_shape_index; } } } } } - - 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; - } } - dirty_quadrant_list.remove(dirty_quadrant_list.first()); - quadrant_order_dirty = true; + q_list_element = q_list_element->next(); } +} - pending_update = false; +void TileMap::_physics_create_quadrant(TileMapQuadrant *p_quadrant) { + ERR_FAIL_COND(!tile_set.is_valid()); - 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()) { - RS::get_singleton()->canvas_item_set_draw_index(F->get(), index++); - } + //Get the TileMap's gobla transform. + Transform2D global_transform; + if (is_inside_tree()) { + global_transform = get_global_transform(); + } + + // Clear all bodies. + p_quadrant->bodies.clear(); + + // Create the body and set its parameters. + for (int layer = 0; layer < tile_set->get_physics_layers_count(); layer++) { + RID body = PhysicsServer2D::get_singleton()->body_create(); + PhysicsServer2D::get_singleton()->body_set_mode(body, PhysicsServer2D::BODY_MODE_STATIC); + + PhysicsServer2D::get_singleton()->body_attach_object_instance_id(body, get_instance_id()); + PhysicsServer2D::get_singleton()->body_set_collision_layer(body, tile_set->get_physics_layer_collision_layer(layer)); + PhysicsServer2D::get_singleton()->body_set_collision_mask(body, tile_set->get_physics_layer_collision_mask(layer)); + + Ref<PhysicsMaterial> physics_material = tile_set->get_physics_layer_physics_material(layer); + if (!physics_material.is_valid()) { + PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, 0); + PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, 1); + } else { + PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, physics_material->computed_bounce()); + PhysicsServer2D::get_singleton()->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, physics_material->computed_friction()); } - quadrant_order_dirty = false; + 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); } +} - _recompute_rect_cache(); +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::_recompute_rect_cache() { -#ifdef DEBUG_ENABLED +void TileMap::_physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { + // Draw the debug collision shapes. + ERR_FAIL_COND(!tile_set.is_valid()); - if (!rect_cache_dirty) { + if (!get_tree()) { return; } - Rect2 r_total; - for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { - 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); - } + 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; } - rect_cache = r_total; + RenderingServer *rs = RenderingServer::get_singleton(); - item_rect_changed(); + Vector2 quadrant_pos = map_to_world(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer)); - rect_cache_dirty = false; -#endif -} + 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); -Map<TileMap::PosKey, TileMap::Quadrant>::Element *TileMap::_create_quadrant(const PosKey &p_qk) { - 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; - } + 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); - 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); + if (tile_set->has_source(c.source_id)) { + TileSetSource *source = *tile_set->get_source(c.source_id); - 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); + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } - 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); + 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); + } + } + } + } } - - 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; + rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, Transform2D()); } +}; - rect_cache_dirty = true; - quadrant_order_dirty = true; - return quadrant_map.insert(p_qk, q); -} +/////////////////////////////// Navigation ////////////////////////////////////// -void TileMap::_erase_quadrant(Map<PosKey, Quadrant>::Element *Q) { - 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); +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; } +} - for (List<RID>::Element *E = q.canvas_items.front(); E; E = E->next()) { - 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::_navigation_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { + ERR_FAIL_COND(!is_inside_tree()); + ERR_FAIL_COND(!tile_set.is_valid()); + + // Get colors for debug. + SceneTree *st = SceneTree::get_singleton(); + Color debug_navigation_color; + bool debug_navigation = st && st->is_debugging_navigation_hint(); + if (debug_navigation) { + debug_navigation_color = st->get_debug_navigation_color(); } - 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()); + 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.navpoly_ids.clear(); - } + q.navigation_regions.clear(); - for (Map<PosKey, Quadrant::Occluder>::Element *E = q.occluder_instances.front(); E; E = E->next()) { - RS::get_singleton()->free(E->get().id); - } - q.occluder_instances.clear(); + // Get the navigation polygons and create regions. + for (Set<Vector2i>::Element *E_cell = q.cells.front(); E_cell; E_cell = E_cell->next()) { + TileMapCell c = get_cell(q.layer, E_cell->get(), true); - quadrant_map.erase(Q); - rect_cache_dirty = true; + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); + + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } + + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); + q.navigation_regions[E_cell->get()].resize(tile_set->get_navigation_layers_count()); + + for (int layer_index = 0; layer_index < tile_set->get_navigation_layers_count(); layer_index++) { + Ref<NavigationPolygon> navpoly; + navpoly = tile_data->get_navigation_polygon(layer_index); + + if (navpoly.is_valid()) { + Transform2D tile_transform; + tile_transform.set_origin(map_to_world(E_cell->get())); + + RID region = NavigationServer2D::get_singleton()->region_create(); + NavigationServer2D::get_singleton()->region_set_map(region, get_world_2d()->get_navigation_map()); + NavigationServer2D::get_singleton()->region_set_transform(region, tilemap_xform * tile_transform); + NavigationServer2D::get_singleton()->region_set_navpoly(region, navpoly); + q.navigation_regions[E_cell->get()].write[layer_index] = region; + } + } + } + } + } + + q_list_element = q_list_element->next(); + } } -void TileMap::_make_quadrant_dirty(Map<PosKey, Quadrant>::Element *Q, bool update) { - Quadrant &q = Q->get(); - if (!q.dirty_list.in_list()) { - dirty_quadrant_list.add(&q.dirty_list); +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(); +} - if (pending_update) { +void TileMap::_navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { + // Draw the debug collision shapes. + ERR_FAIL_COND(!tile_set.is_valid()); + + if (!get_tree()) { return; } - pending_update = true; - if (!is_inside_tree()) { + + bool show_navigation = false; + switch (navigation_visibility_mode) { + case TileMap::VISIBILITY_MODE_DEFAULT: + show_navigation = !Engine::get_singleton()->is_editor_hint() && (get_tree() && get_tree()->is_debugging_navigation_hint()); + break; + case TileMap::VISIBILITY_MODE_FORCE_HIDE: + show_navigation = false; + break; + case TileMap::VISIBILITY_MODE_FORCE_SHOW: + show_navigation = true; + break; + } + if (!show_navigation) { return; } - if (update) { - call_deferred("update_dirty_quadrants"); - } -} + RenderingServer *rs = RenderingServer::get_singleton(); -void TileMap::set_cellv(const Vector2 &p_pos, int p_tile, bool p_flip_x, bool p_flip_y, bool p_transpose) { - set_cell(p_pos.x, p_pos.y, p_tile, p_flip_x, p_flip_y, p_transpose); -} + Color color = get_tree()->get_debug_navigation_color(); + RandomPCG rand; -void TileMap::_set_celld(const Vector2 &p_pos, const Dictionary &p_data) { - 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); -} + Vector2 quadrant_pos = map_to_world(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer)); -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) { - PosKey pk(p_x, p_y); + 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); - Map<PosKey, Cell>::Element *E = tile_map.find(pk); - if (!E && p_tile == INVALID_CELL) { - return; //nothing to do - } + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); - 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); - } + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } - used_size_cache_dirty = true; - return; - } + 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)); - Map<PosKey, Quadrant>::Element *Q = quadrant_map.find(qk); + 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]]; + } - if (!E) { - E = tile_map.insert(pk, Cell()); - if (!Q) { - Q = _create_quadrant(qk); - } - Quadrant &q = Q->get(); - q.cells.insert(pk); - } else { - ERR_FAIL_COND(!Q); // quadrant should exist... + // 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); - 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 + rs->canvas_item_add_polygon(p_quadrant->debug_canvas_item, vertices, colors); + } + } + } + } } } +} - Cell &c = E->get(); +/////////////////////////////// Scenes ////////////////////////////////////// - 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; +void TileMap::_scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { + ERR_FAIL_COND(!tile_set.is_valid()); - _make_quadrant_dirty(Q); - used_size_cache_dirty = true; -} + SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); + while (q_list_element) { + TileMapQuadrant &q = *q_list_element->self(); -int TileMap::get_cellv(const Vector2 &p_pos) const { - return get_cell(p_pos.x, p_pos.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(); + } + } + + q.scenes.clear(); + + // Recreate the scenes. + for (Set<Vector2i>::Element *E_cell = q.cells.front(); E_cell; E_cell = E_cell->next()) { + const TileMapCell &c = get_cell(q.layer, E_cell->get(), true); + + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); + + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } -void TileMap::make_bitmask_area_dirty(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++) { - PosKey p(x, y); - if (dirty_bitmask.find(p) == nullptr) { - dirty_bitmask.push_back(p); + TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source); + if (scenes_collection_source) { + Ref<PackedScene> packed_scene = scenes_collection_source->get_scene_tile_scene(c.alternative_tile); + if (packed_scene.is_valid()) { + Node *scene = packed_scene->instantiate(); + add_child(scene); + Control *scene_as_control = Object::cast_to<Control>(scene); + Node2D *scene_as_node2d = Object::cast_to<Node2D>(scene); + if (scene_as_control) { + scene_as_control->set_position(map_to_world(E_cell->get()) + scene_as_control->get_position()); + } else if (scene_as_node2d) { + Transform2D xform; + xform.set_origin(map_to_world(E_cell->get())); + scene_as_node2d->set_transform(xform * scene_as_node2d->get_transform()); + } + q.scenes[E_cell->get()] = scene->get_name(); + } + } } } + + q_list_element = q_list_element->next(); } } -void TileMap::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::_scenes_cleanup_quadrant(TileMapQuadrant *p_quadrant) { + // Clear the scenes. + for (Map<Vector2i, String>::Element *E = p_quadrant->scenes.front(); E; E = E->next()) { + Node *node = get_node(E->get()); + if (node) { + node->queue_delete(); } } + + p_quadrant->scenes.clear(); } -void TileMap::update_bitmask_region(const Vector2 &p_start, const Vector2 &p_end) { - 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)) { - Array a = get_used_cells(); - for (int i = 0; i < a.size(); i++) { - Vector2 vector = (Vector2)a[i]; - update_cell_bitmask(vector.x, vector.y); - } +void TileMap::_scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { + ERR_FAIL_COND(!tile_set.is_valid()); + + if (!Engine::get_singleton()->is_editor_hint()) { 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); - } - } -} -void TileMap::update_cell_bitmask(int p_x, int p_y) { - 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; + // 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); - PosKey qk = p.to_quadrant(_get_quadrant_size()); - Map<PosKey, Quadrant>::Element *Q = quadrant_map.find(qk); - _make_quadrant_dirty(Q); + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); - } else if (tile_set->tile_get_tile_mode(id) == TileSet::SINGLE_TILE) { - 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->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; + 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_dirty_bitmask() { - while (dirty_bitmask.size() > 0) { - update_cell_bitmask(dirty_bitmask[0].x, dirty_bitmask[0].y); - dirty_bitmask.pop_front(); +void TileMap::set_cell(int p_layer, const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); + + // Set the current cell tile (using integer position). + Map<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; + Vector2i pk(p_coords); + Map<Vector2i, TileMapCell>::Element *E = tile_map.find(pk); + + int source_id = p_source_id; + Vector2i atlas_coords = p_atlas_coords; + int alternative_tile = p_alternative_tile; + + 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; } -} -void TileMap::fix_invalid_tiles() { - ERR_FAIL_COND_MSG(tile_set.is_null(), "Cannot fix invalid tiles if Tileset is not open."); + if (!E && source_id == TileSet::INVALID_SOURCE) { + return; // Nothing to do, the tile is already empty. + } + + // Get the quadrant + Vector2i qk = _coords_to_quadrant_coords(p_layer, pk); + + Map<Vector2i, TileMapQuadrant>::Element *Q = layers[p_layer].quadrant_map.find(qk); + + if (source_id == TileSet::INVALID_SOURCE) { + // Erase existing cell in the tile map. + tile_map.erase(pk); - Map<PosKey, Cell> temp_tile_map = tile_map; - for (Map<PosKey, Cell>::Element *E = temp_tile_map.front(); E; E = E->next()) { - if (!tile_set->has_tile(get_cell(E->key().x, E->key().y))) { - set_cell(E->key().x, E->key().y, INVALID_CELL); + // Erase existing cell in the quadrant. + ERR_FAIL_COND(!Q); + TileMapQuadrant &q = Q->get(); + + q.cells.erase(pk); + + // Remove or make the quadrant dirty. + if (q.cells.size() == 0) { + _erase_quadrant(Q); + } else { + _make_quadrant_dirty(Q); } - } -} -int TileMap::get_cell(int p_x, int p_y) const { - PosKey pk(p_x, p_y); + used_rect_cache_dirty = true; + } else { + if (!E) { + // Insert a new cell in the tile map. + E = tile_map.insert(pk, TileMapCell()); - const Map<PosKey, Cell>::Element *E = tile_map.find(pk); + // 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); - if (!E) { - return INVALID_CELL; - } + } else { + ERR_FAIL_COND(!Q); // TileMapQuadrant should exist... - return E->get().id; -} + if (E->get().source_id == source_id && E->get().get_atlas_coords() == atlas_coords && E->get().alternative_tile == alternative_tile) { + return; // Nothing changed. + } + } -bool TileMap::is_cell_x_flipped(int p_x, int p_y) const { - PosKey pk(p_x, p_y); + TileMapCell &c = E->get(); - const Map<PosKey, Cell>::Element *E = tile_map.find(pk); + c.source_id = source_id; + c.set_atlas_coords(atlas_coords); + c.alternative_tile = alternative_tile; - if (!E) { - return false; + _make_quadrant_dirty(Q); + used_rect_cache_dirty = true; } - - return E->get().flip_h; } -bool TileMap::is_cell_y_flipped(int p_x, int p_y) const { - PosKey pk(p_x, p_y); +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); - const Map<PosKey, Cell>::Element *E = tile_map.find(pk); + // Get a cell source id from position + const Map<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; + const Map<Vector2i, TileMapCell>::Element *E = tile_map.find(p_coords); if (!E) { - return false; + return TileSet::INVALID_SOURCE; } - return E->get().flip_v; + if (p_use_proxies && tile_set.is_valid()) { + Array proxyed = tile_set->map_tile_proxy(E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + return proxyed[0]; + } + + return E->get().source_id; } -bool TileMap::is_cell_transposed(int p_x, int p_y) const { - PosKey pk(p_x, p_y); +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); - const Map<PosKey, Cell>::Element *E = tile_map.find(pk); + // Get a cell source id from position + const Map<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; + const Map<Vector2i, TileMapCell>::Element *E = tile_map.find(p_coords); if (!E) { - return false; + return TileSetSource::INVALID_ATLAS_COORDS; + } + + if (p_use_proxies && tile_set.is_valid()) { + Array proxyed = tile_set->map_tile_proxy(E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + return proxyed[1]; } - return E->get().transpose; + return E->get().get_atlas_coords(); } -void TileMap::set_cell_autotile_coord(int p_x, int p_y, const Vector2 &p_coord) { - PosKey pk(p_x, p_y); +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); - const Map<PosKey, Cell>::Element *E = tile_map.find(pk); + // Get a cell source id from position + const Map<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; + const Map<Vector2i, TileMapCell>::Element *E = tile_map.find(p_coords); if (!E) { - return; + return TileSetSource::INVALID_TILE_ALTERNATIVE; } - Cell c = E->get(); - c.autotile_coord_x = p_coord.x; - c.autotile_coord_y = p_coord.y; - tile_map[pk] = c; - - PosKey qk = pk.to_quadrant(_get_quadrant_size()); - Map<PosKey, Quadrant>::Element *Q = quadrant_map.find(qk); - - if (!Q) { - return; + 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]; } - _make_quadrant_dirty(Q); + return E->get().alternative_tile; } -Vector2 TileMap::get_cell_autotile_coord(int p_x, int p_y) const { - PosKey pk(p_x, p_y); +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); - const Map<PosKey, Cell>::Element *E = tile_map.find(pk); + TileMapPattern *output = memnew(TileMapPattern); + if (p_coords_array.is_empty()) { + return output; + } - if (!E) { - return Vector2(); + Vector2i min = Vector2i(p_coords_array[0]); + for (int i = 1; i < p_coords_array.size(); i++) { + min = min.min(p_coords_array[i]); } - return Vector2(E->get().autotile_coord_x, E->get().autotile_coord_y); -} + 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; + } -void TileMap::_recreate_quadrants() { - _clear_quadrants(); + 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)); + } - for (Map<PosKey, Cell>::Element *E = tile_map.front(); E; E = E->next()) { - PosKey qk = PosKey(E->key().x, E->key().y).to_quadrant(_get_quadrant_size()); + return output; +} - Map<PosKey, Quadrant>::Element *Q = quadrant_map.find(qk); - if (!Q) { - Q = _create_quadrant(qk); - dirty_quadrant_list.add(&Q->get().dirty_list); - } +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()); - Q->get().cells.insert(E->key()); - _make_quadrant_dirty(Q, false); + 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; + } + } } - update_dirty_quadrants(); + + return output; } -void TileMap::_clear_quadrants() { - while (quadrant_map.size()) { - _erase_quadrant(quadrant_map.front()); +void TileMap::set_pattern(int p_layer, Vector2i p_position, const TileMapPattern *p_pattern) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); + ERR_FAIL_COND(!tile_set.is_valid()); + + TypedArray<Vector2i> used_cells = p_pattern->get_used_cells(); + for (int i = 0; i < used_cells.size(); i++) { + Vector2i coords = map_pattern(p_position, used_cells[i], p_pattern); + set_cell(p_layer, coords, p_pattern->get_cell_source_id(coords), p_pattern->get_cell_atlas_coords(coords), p_pattern->get_cell_alternative_tile(coords)); } } -void TileMap::set_material(const Ref<Material> &p_material) { - CanvasItem::set_material(p_material); - _update_all_items_material_state(); +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; + } } -void TileMap::set_use_parent_material(bool p_use_parent_material) { - CanvasItem::set_use_parent_material(p_use_parent_material); - _update_all_items_material_state(); +Map<Vector2i, TileMapQuadrant> *TileMap::get_quadrant_map(int p_layer) { + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), nullptr); + + return &layers[p_layer].quadrant_map; } -void TileMap::_update_all_items_material_state() { - 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()) { - _update_item_material_state(F->get()); +void TileMap::fix_invalid_tiles() { + ERR_FAIL_COND_MSG(tile_set.is_null(), "Cannot fix invalid tiles if Tileset is not open."); + + for (unsigned int i = 0; i < layers.size(); i++) { + const Map<Vector2i, TileMapCell> &tile_map = layers[i].tile_map; + Set<Vector2i> coords; + 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::_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_layer(int p_layer) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); + + // Remove all tiles. + _clear_layer_internals(p_layer); + layers[p_layer].tile_map.clear(); + + used_rect_cache_dirty = true; } void TileMap::clear() { - _clear_quadrants(); - tile_map.clear(); - used_size_cache_dirty = true; + // 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_data(const Vector<int> &p_data) { - ERR_FAIL_COND(format > FORMAT_2); +void TileMap::_set_tile_data(int p_layer, const Vector<int> &p_data) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); + ERR_FAIL_COND(format > FORMAT_3); + + // Set data for a given tile from raw data. int c = p_data.size(); const int *r = p_data.ptr(); - int offset = (format == FORMAT_2) ? 3 : 2; + int offset = (format >= FORMAT_2) ? 3 : 2; + ERR_FAIL_COND_MSG(c % offset != 0, "Corrupted tile data."); + + clear_layer(p_layer); + +#ifdef DISABLE_DEPRECATED + ERR_FAIL_COND_MSG(format != FORMAT_3, vformat("Cannot handle deprecated TileMap data format version %d. This Godot version was compiled with no support for deprecated data.", format)); +#endif - clear(); 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++) { + for (int j = 0; j < ((format >= FORMAT_2) ? 12 : 8); j++) { local[j] = ptr[j]; } @@ -1191,31 +1905,62 @@ void TileMap::_set_tile_data(const Vector<int> &p_data) { SWAP(local[4], local[7]); SWAP(local[5], local[6]); //TODO: ask someone to check this... - if (FORMAT == FORMAT_2) { + 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]); + } - 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]); + 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 } - - set_cell(x, y, v, flip_h, flip_v, transpose, Vector2(coord_x, coord_y)); } + emit_signal(SNAME("changed")); } -Vector<int> TileMap::_get_tile_data() const { +Vector<int> TileMap::_get_tile_data(int p_layer) const { + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), Vector<int>()); + + // Export tile data to raw format + const Map<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; Vector<int> data; data.resize(tile_map.size() * 3); int *w = data.ptrw(); @@ -1223,23 +1968,14 @@ Vector<int> TileMap::_get_tile_data() const { // Save in highest format int idx = 0; - for (const Map<PosKey, Cell>::Element *E = tile_map.front(); E; E = E->next()) { + for (const Map<Vector2i, TileMapCell>::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]); + 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; } @@ -1248,8 +1984,9 @@ Vector<int> TileMap::_get_tile_data() const { #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(); + const_cast<TileMap *>(this)->_update_dirty_quadrants(); } else { const_cast<TileMap *>(this)->_recompute_rect_cache(); } @@ -1257,361 +1994,730 @@ Rect2 TileMap::_edit_get_rect() const { } #endif -void TileMap::set_collision_layer(uint32_t p_layer) { - collision_layer = p_layer; - 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_layer(q.body, collision_layer); - } - } -} - -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); - } - } -} - -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); -} - -bool TileMap::get_collision_use_kinematic() const { - return use_kinematic; -} - -void TileMap::set_collision_use_kinematic(bool p_use_kinematic) { - _clear_quadrants(); - use_kinematic = p_use_kinematic; - _recreate_quadrants(); -} - -bool TileMap::get_collision_use_parent() const { - return use_parent; -} - -void TileMap::set_collision_use_parent(bool p_use_parent) { - if (use_parent == p_use_parent) { - return; - } - - _clear_quadrants(); - - use_parent = p_use_parent; - set_notify_local_transform(use_parent); - - if (use_parent && is_inside_tree()) { - collision_parent = Object::cast_to<CollisionObject2D>(get_parent()); - } else { - collision_parent = nullptr; - } - - _recreate_quadrants(); - notify_property_list_changed(); - update_configuration_warning(); -} - -void TileMap::set_collision_friction(float p_friction) { - friction = p_friction; - 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_FRICTION, p_friction); - } - } -} - -float TileMap::get_collision_friction() const { - return friction; -} - -void TileMap::set_collision_bounce(float p_bounce) { - 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); - } - } -} - -float TileMap::get_collision_bounce() const { - return bounce; -} - -uint32_t TileMap::get_collision_layer() const { - return collision_layer; -} - -uint32_t TileMap::get_collision_mask() const { - return collision_mask; -} - -bool TileMap::get_collision_layer_bit(int p_bit) const { - return get_collision_layer() & (1 << p_bit); -} - -bool TileMap::get_collision_mask_bit(int p_bit) const { - return get_collision_mask() & (1 << p_bit); -} - -void TileMap::set_mode(Mode p_mode) { - _clear_quadrants(); - mode = p_mode; - _recreate_quadrants(); - emit_signal("settings_changed"); -} - -TileMap::Mode TileMap::get_mode() const { - return mode; -} - -void TileMap::set_half_offset(HalfOffset p_half_offset) { - _clear_quadrants(); - half_offset = p_half_offset; - _recreate_quadrants(); - emit_signal("settings_changed"); -} - -void TileMap::set_tile_origin(TileOrigin p_tile_origin) { - _clear_quadrants(); - tile_origin = p_tile_origin; - _recreate_quadrants(); - emit_signal("settings_changed"); -} - -TileMap::TileOrigin TileMap::get_tile_origin() const { - return tile_origin; -} - -Vector2 TileMap::get_cell_draw_offset() const { - switch (mode) { - case MODE_SQUARE: { - return Vector2(); - } break; - case MODE_ISOMETRIC: { - return Vector2(-cell_size.x * 0.5, 0); - - } 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; - } - - return Vector2(); -} - -TileMap::HalfOffset TileMap::get_half_offset() const { - return half_offset; -} - -Transform2D TileMap::get_cell_transform() const { - switch (mode) { - case MODE_SQUARE: { - 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; - - } break; - case MODE_CUSTOM: { - return custom_transform; - } break; - } - - 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; -} - -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. - } - } - } - return ret; -} - 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); -} + p_list->push_back(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::NIL, "Layers", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); + for (unsigned int i = 0; i < layers.size(); i++) { + p_list->push_back(PropertyInfo(Variant::STRING, vformat("layer_%d/name", i), PROPERTY_HINT_NONE)); + p_list->push_back(PropertyInfo(Variant::BOOL, vformat("layer_%d/enabled", i), PROPERTY_HINT_NONE)); + p_list->push_back(PropertyInfo(Variant::BOOL, vformat("layer_%d/y_sort_enabled", i), PROPERTY_HINT_NONE)); + p_list->push_back(PropertyInfo(Variant::INT, vformat("layer_%d/y_sort_origin", i), PROPERTY_HINT_NONE)); + p_list->push_back(PropertyInfo(Variant::INT, vformat("layer_%d/z_index", i), PROPERTY_HINT_NONE)); + p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("layer_%d/tile_data", i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + } +} + +Vector2 TileMap::map_to_world(const Vector2i &p_pos) const { + // SHOULD RETURN THE CENTER OF THE TILE + ERR_FAIL_COND_V(!tile_set.is_valid(), Vector2()); + + Vector2 ret = p_pos; + TileSet::TileShape tile_shape = tile_set->get_tile_shape(); + TileSet::TileOffsetAxis tile_offset_axis = tile_set->get_tile_offset_axis(); + + 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; + } + } 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; + } + } + } -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; + // 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; } -} -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); + return (ret + Vector2(0.5, 0.5)) * tile_set->get_tile_size(); } -Vector2 TileMap::world_to_map(const Vector2 &p_pos) const { - Vector2 ret = get_cell_transform().affine_inverse().xform(p_pos); +Vector2i TileMap::world_to_map(const Vector2 &p_pos) const { + ERR_FAIL_COND_V(!tile_set.is_valid(), Vector2i()); + + Vector2 ret = p_pos; + ret /= tile_set->get_tile_size(); - switch (half_offset) { - case HALF_OFFSET_X: { - if (int(floor(ret.y)) & 1) { - ret.x -= 0.5; + 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(); + + // 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(); } - } break; - case HALF_OFFSET_NEGATIVE_X: { - if (int(floor(ret.y)) & 1) { - ret.x += 0.5; + + // 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; } - } break; - case HALF_OFFSET_Y: { - if (int(floor(ret.x)) & 1) { - ret.y -= 0.5; + } 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(); } - } break; - case HALF_OFFSET_NEGATIVE_Y: { - if (int(floor(ret.x)) & 1) { - ret.y += 0.5; + + // 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; } - } break; - case HALF_OFFSET_DISABLED: { - // Nothing to do. + } + } 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; } } - - // 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_enabled(bool p_enable) { - _clear_quadrants(); - use_y_sort = p_enable; - RS::get_singleton()->canvas_item_set_sort_children_by_y(get_canvas_item(), use_y_sort); - _recreate_quadrants(); - emit_signal("settings_changed"); -} +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); + } -bool TileMap::is_y_sort_enabled() const { - return use_y_sort; -} + } 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); + } -void TileMap::set_compatibility_mode(bool p_enable) { - _clear_quadrants(); - compatibility_mode = 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(-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); + } -bool TileMap::is_compatibility_mode_enabled() const { - return compatibility_mode; -} + } 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); + } -void TileMap::set_centered_textures(bool p_enable) { - _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(-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); + } + } } -bool TileMap::is_centered_textures_enabled() const { - return centered_textures; -} +TypedArray<Vector2i> TileMap::get_used_cells(int p_layer) const { + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), TypedArray<Vector2i>()); -TypedArray<Vector2i> TileMap::get_used_cells() const { + // Returns the cells used in the tilemap. TypedArray<Vector2i> a; - a.resize(tile_map.size()); + a.resize(layers[p_layer].tile_map.size()); int i = 0; - for (Map<PosKey, Cell>::Element *E = tile_map.front(); E; E = E->next()) { + for (Map<Vector2i, TileMapCell>::Element *E = layers[p_layer].tile_map.front(); E; E = E->next()) { Vector2i p(E->key().x, E->key().y); a[i++] = p; } @@ -1619,255 +2725,314 @@ TypedArray<Vector2i> TileMap::get_used_cells() const { return a; } -TypedArray<Vector2i> TileMap::get_used_cells_by_index(int p_id) const { - TypedArray<Vector2i> a; - for (Map<PosKey, Cell>::Element *E = tile_map.front(); E; E = E->next()) { - if (E->value().id == p_id) { - Vector2i p(E->key().x, E->key().y); - a.push_back(p); - } - } - - return a; -} - 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; + } - 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); - - 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)); + for (Map<Vector2i, TileMapCell>::Element *E = tile_map.front(); E; E = E->next()) { + used_rect_cache.expand_to(Vector2i(E->key().x, E->key().y)); + } } - - used_size_cache.size += Vector2(1, 1); - } else { - used_size_cache = Rect2(); } - used_size_cache_dirty = false; - } - - 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()) { - 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); + 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; } -} -int TileMap::get_occluder_light_mask() const { - return occluder_light_mask; + return used_rect_cache; } +// --- Override some methods of the CanvasItem class to pass the changes to the quadrants CanvasItems --- + void TileMap::set_light_mask(int p_light_mask) { + // Occlusion: set light mask. CanvasItem::set_light_mask(p_light_mask); - for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { - for (List<RID>::Element *F = E->get().canvas_items.front(); F; F = F->next()) { - RenderingServer::get_singleton()->canvas_item_set_light_mask(F->get(), get_light_mask()); + for (unsigned int layer = 0; layer < layers.size(); layer++) { + for (Map<Vector2i, TileMapQuadrant>::Element *E = layers[layer].quadrant_map.front(); E; E = E->next()) { + for (const RID &ci : E->get().canvas_items) { + RenderingServer::get_singleton()->canvas_item_set_light_mask(ci, get_light_mask()); + } } + _rendering_update_layer(layer); } } -void TileMap::set_clip_uv(bool p_enable) { - if (clip_uv == p_enable) { - return; - } +void TileMap::set_material(const Ref<Material> &p_material) { + // Set material for the whole tilemap. + CanvasItem::set_material(p_material); - _clear_quadrants(); - clip_uv = p_enable; - _recreate_quadrants(); + // 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); + } } -bool TileMap::get_clip_uv() const { - return clip_uv; +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); + + // 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_texture_filter(TextureFilter p_texture_filter) { + // Set a default texture filter for the whole tilemap CanvasItem::set_texture_filter(p_texture_filter); - for (Map<PosKey, Quadrant>::Element *F = quadrant_map.front(); F; F = F->next()) { - Quadrant &q = F->get(); - for (List<RID>::Element *E = q.canvas_items.front(); E; E = E->next()) { - RenderingServer::get_singleton()->canvas_item_set_default_texture_filter(E->get(), RS::CanvasItemTextureFilter(p_texture_filter)); - _make_quadrant_dirty(F); + for (unsigned int layer = 0; layer < layers.size(); layer++) { + for (Map<Vector2i, TileMapQuadrant>::Element *F = layers[layer].quadrant_map.front(); F; F = F->next()) { + TileMapQuadrant &q = F->get(); + for (const RID &ci : q.canvas_items) { + RenderingServer::get_singleton()->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(p_texture_filter)); + _make_quadrant_dirty(F); + } } + _rendering_update_layer(layer); } } void TileMap::set_texture_repeat(CanvasItem::TextureRepeat p_texture_repeat) { + // Set a default texture repeat for the whole tilemap CanvasItem::set_texture_repeat(p_texture_repeat); - for (Map<PosKey, Quadrant>::Element *F = quadrant_map.front(); F; F = F->next()) { - Quadrant &q = F->get(); - for (List<RID>::Element *E = q.canvas_items.front(); E; E = E->next()) { - RenderingServer::get_singleton()->canvas_item_set_default_texture_repeat(E->get(), RS::CanvasItemTextureRepeat(p_texture_repeat)); - _make_quadrant_dirty(F); + for (unsigned int layer = 0; layer < layers.size(); layer++) { + for (Map<Vector2i, TileMapQuadrant>::Element *F = layers[layer].quadrant_map.front(); F; F = F->next()) { + TileMapQuadrant &q = F->get(); + for (const RID &ci : q.canvas_items) { + RenderingServer::get_singleton()->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(p_texture_repeat)); + _make_quadrant_dirty(F); + } } + _rendering_update_layer(layer); } } -String TileMap::get_configuration_warning() const { - String warning = Node2D::get_configuration_warning(); +TypedArray<Vector2i> TileMap::get_surrounding_tiles(Vector2i coords) { + if (!tile_set.is_valid()) { + return TypedArray<Vector2i>(); + } - if (use_parent && !collision_parent) { - if (!warning.is_empty()) { - warning += "\n\n"; + 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)); } - 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 around; } -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_enabled", "enable"), &TileMap::set_y_sort_enabled); - ClassDB::bind_method(D_METHOD("is_y_sort_enabled"), &TileMap::is_y_sort_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); +void TileMap::draw_cells_outline(Control *p_control, Set<Vector2i> p_cells, Color p_color, Transform2D p_transform) { + if (!tile_set.is_valid()) { + return; + } - 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); + // 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(); - 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); + for (Set<Vector2i>::Element *E = p_cells.front(); E; E = E->next()) { + Vector2 center = map_to_world(E->get()); - 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); +#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); \ + } - 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); + 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 +} - 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); +TypedArray<String> TileMap::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); - 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); + // 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); + } + } - 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); + // 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; + } + } - 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); + return warnings; +} - 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); +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_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("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("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_index", "index"), &TileMap::get_used_cells_by_index); + 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_enabled", "is_y_sort_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(PropertyInfo(Variant::INT, "collision_visibility_mode", PROPERTY_HINT_ENUM, "Default,Force Show,Force Hide"), "set_collision_visibility_mode", "get_collision_visibility_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_visibility_mode", PROPERTY_HINT_ENUM, "Default,Force Show,Force Hide"), "set_navigation_visibility_mode", "get_navigation_visibility_mode"); - ADD_PROPERTY_DEFAULT("format", FORMAT_1); - - ADD_SIGNAL(MethodInfo("settings_changed")); + ADD_ARRAY("layers", "layer_"); - BIND_CONSTANT(INVALID_CELL); + ADD_PROPERTY_DEFAULT("format", FORMAT_1); - BIND_ENUM_CONSTANT(MODE_SQUARE); - BIND_ENUM_CONSTANT(MODE_ISOMETRIC); - BIND_ENUM_CONSTANT(MODE_CUSTOM); + ADD_SIGNAL(MethodInfo("changed")); - 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); + BIND_ENUM_CONSTANT(VISIBILITY_MODE_DEFAULT); + BIND_ENUM_CONSTANT(VISIBILITY_MODE_FORCE_HIDE); + BIND_ENUM_CONSTANT(VISIBILITY_MODE_FORCE_SHOW); +} - BIND_ENUM_CONSTANT(TILE_ORIGIN_TOP_LEFT); - BIND_ENUM_CONSTANT(TILE_ORIGIN_CENTER); - BIND_ENUM_CONSTANT(TILE_ORIGIN_BOTTOM_LEFT); +void TileMap::_tile_set_changed() { + emit_signal(SNAME("changed")); + _clear_internals(); + _recreate_internals(); } TileMap::TileMap() { set_notify_transform(true); set_notify_local_transform(false); + + layers.resize(1); } TileMap::~TileMap() { - 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 3bf4587921..3ac50fc7cc 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -31,197 +31,254 @@ #ifndef TILE_MAP_H #define TILE_MAP_H -#include "core/templates/self_list.h" -#include "core/templates/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 +union TileMapCell { + struct { + int32_t source_id : 16; + int16_t coord_x : 16; + int16_t coord_y : 16; + int32_t alternative_tile : 16; }; - enum HalfOffset { - HALF_OFFSET_X, - HALF_OFFSET_Y, - HALF_OFFSET_DISABLED, - HALF_OFFSET_NEGATIVE_X, - HALF_OFFSET_NEGATIVE_Y, - }; + 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; + } + } - enum TileOrigin { - TILE_ORIGIN_TOP_LEFT, - TILE_ORIGIN_CENTER, - TILE_ORIGIN_BOTTOM_LEFT - }; + 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); + } +}; -private: - enum DataFormat { - FORMAT_1 = 0, - FORMAT_2 +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; + } + } }; - Ref<TileSet> tile_set; - Size2i cell_size = Size2(64, 64); - int quadrant_size = 16; - Mode mode = MODE_SQUARE; - Transform2D custom_transform = Transform2D(64, 0, 0, 64, 0, 0); - HalfOffset half_offset = HALF_OFFSET_DISABLED; - bool use_parent = false; - CollisionObject2D *collision_parent = nullptr; - bool use_kinematic = false; - Navigation2D *navigation = nullptr; - - union PosKey { - struct { - int16_t x; - int16_t y; - }; - uint32_t key = 0; - - //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); - } + // 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) { + } +}; - PosKey(int16_t p_x, int16_t p_y) { - x = p_x; - y = p_y; - } - PosKey() { - x = 0; - y = 0; - } - }; +class TileMapPattern : public Object { + GDCLASS(TileMapPattern, Object); - union Cell { - 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; - }; - - uint64_t _u64t = 0; - }; + Vector2i size; + Map<Vector2i, TileMapCell> pattern; - Map<PosKey, Cell> tile_map; - List<PosKey> dirty_bitmask; +protected: + static void _bind_methods(); - struct Quadrant { - Vector2 pos; - List<RID> canvas_items; - RID body; - uint32_t shape_owner_id = 0; +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; + + 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; + // Updates. bool pending_update = false; + // Rect. Rect2 rect_cache; bool rect_cache_dirty = true; - Rect2 used_size_cache; - bool used_size_cache_dirty = true; - bool quadrant_order_dirty = false; - bool use_y_sort = false; - bool compatibility_mode = false; - bool centered_textures = false; - bool clip_uv = false; - float fp_adjust = 0.00001; - float friction = 1.0; - float bounce = 0.0; - uint32_t collision_layer = 1; - uint32_t collision_mask = 1; - mutable DataFormat format = FORMAT_1; // Assume lowest possible format if none is present - - TileOrigin tile_origin = TILE_ORIGIN_TOP_LEFT; - - int occluder_light_mask = 1; - - 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(); + 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; - void _update_all_items_material_state(); - _FORCE_INLINE_ void _update_item_material_state(const RID &p_canvas_item); + // Quadrants and internals management. + Vector2i _coords_to_quadrant_coords(int p_layer, const Vector2i &p_coords) const; - _FORCE_INLINE_ int _get_quadrant_size() const; + Map<Vector2i, TileMapQuadrant>::Element *_create_quadrant(int p_layer, const Vector2i &p_qk); - void _set_tile_data(const Vector<int> &p_data); - Vector<int> _get_tile_data() const; + void _make_quadrant_dirty(Map<Vector2i, TileMapQuadrant>::Element *Q); + void _make_all_quadrants_dirty(); + void _queue_update_dirty_quadrants(); - 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 _update_dirty_quadrants(); - _FORCE_INLINE_ Vector2 _map_to_world(int p_x, int p_y, bool p_ignore_ofs = false) const; + void _recreate_internals(); + + void _erase_quadrant(Map<Vector2i, TileMapQuadrant>::Element *Q); + void _clear_layer_internals(int p_layer); + void _clear_internals(); + + // Rect caching. + void _recompute_rect_cache(); + + // Per-system methods. + bool _rendering_quadrant_order_dirty = false; + void _rendering_notification(int p_what); + void _rendering_update_layer(int p_layer); + void _rendering_cleanup_layer(int p_layer); + void _rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list); + void _rendering_create_quadrant(TileMapQuadrant *p_quadrant); + void _rendering_cleanup_quadrant(TileMapQuadrant *p_quadrant); + void _rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant); + + void _physics_notification(int p_what); + void _physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list); + void _physics_create_quadrant(TileMapQuadrant *p_quadrant); + void _physics_cleanup_quadrant(TileMapQuadrant *p_quadrant); + void _physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant); + + void _navigation_notification(int p_what); + void _navigation_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list); + void _navigation_cleanup_quadrant(TileMapQuadrant *p_quadrant); + void _navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant); + + void _scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list); + void _scenes_cleanup_quadrant(TileMapQuadrant *p_quadrant); + void _scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant); + + // Set and get tiles from data arrays. + void _set_tile_data(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); @@ -231,9 +288,9 @@ protected: void _notification(int p_what); static void _bind_methods(); - virtual void _validate_property(PropertyInfo &property) const override; - 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 }; @@ -245,114 +302,85 @@ public: 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_enabled(bool p_enable); - bool is_y_sort_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; - - TypedArray<Vector2i> get_used_cells() const; - TypedArray<Vector2i> get_used_cells_by_index(int p_index) 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; - + // 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; - - void set_clip_uv(bool p_enable); - bool get_clip_uv() const; - - String get_configuration_warning() const override; - virtual void set_texture_filter(CanvasItem::TextureFilter p_texture_filter) override; - virtual void set_texture_repeat(CanvasItem::TextureRepeat p_texture_repeat) override; + // Fixing a nclearing methods. void fix_invalid_tiles(); + + void clear_layer(int p_layer); void clear(); + // Helpers + TypedArray<Vector2i> get_surrounding_tiles(Vector2i coords); + void draw_cells_outline(Control *p_control, Set<Vector2i> p_cells, Color p_color, Transform2D p_transform = Transform2D()); + + // Configuration warnings. + TypedArray<String> get_configuration_warnings() const override; + TileMap(); ~TileMap(); }; -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 9d6868a1b2..8bd7b696f2 100644 --- a/scene/2d/touch_screen_button.cpp +++ b/scene/2d/touch_screen_button.cpp @@ -30,11 +30,8 @@ #include "touch_screen_button.h" -#include "core/input/input.h" -#include "core/input/input_map.h" -#include "core/os/os.h" #include "scene/main/window.h" -# + void TouchScreenButton::set_texture(const Ref<Texture2D> &p_texture) { texture = p_texture; update(); @@ -188,7 +185,9 @@ 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()) { return; } @@ -286,13 +285,13 @@ void TouchScreenButton::_press(int p_finger_pressed) { if (action != StringName()) { 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(); } @@ -303,15 +302,15 @@ void TouchScreenButton::_release(bool p_exiting_tree) { 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(); } } @@ -385,8 +384,6 @@ void TouchScreenButton::_bind_methods() { ClassDB::bind_method(D_METHOD("is_pressed"), &TouchScreenButton::is_pressed); - ClassDB::bind_method(D_METHOD("_input"), &TouchScreenButton::_input); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "normal", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "pressed", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture_pressed", "get_texture_pressed"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "bitmask", PROPERTY_HINT_RESOURCE_TYPE, "BitMap"), "set_bitmask", "get_bitmask"); diff --git a/scene/2d/touch_screen_button.h b/scene/2d/touch_screen_button.h index 10820ad059..1c515149d4 100644 --- a/scene/2d/touch_screen_button.h +++ b/scene/2d/touch_screen_button.h @@ -61,7 +61,7 @@ private: VisibilityMode visibility = VISIBILITY_ALWAYS; - void _input(const Ref<InputEvent> &p_event); + virtual void input(const Ref<InputEvent> &p_event) override; bool _is_point_inside(const Point2 &p_point); diff --git a/scene/2d/visibility_notifier_2d.cpp b/scene/2d/visibility_notifier_2d.cpp deleted file mode 100644 index 916038a1f3..0000000000 --- a/scene/2d/visibility_notifier_2d.cpp +++ /dev/null @@ -1,364 +0,0 @@ -/*************************************************************************/ -/* visibility_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 "visibility_notifier_2d.h" - -#include "core/config/engine.h" -#include "gpu_particles_2d.h" -#include "scene/2d/animated_sprite_2d.h" -#include "scene/2d/physics_body_2d.h" -#include "scene/animation/animation_player.h" -#include "scene/main/window.h" -#include "scene/scene_string_names.h" - -#ifdef TOOLS_ENABLED -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(); - } - } -} - -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); - - // We need to defer the call of set_process and set_physics_process, - // otherwise they are overwritten inside NOTIFICATION_READY. - // We can't use call_deferred, because it happens after a physics frame. - // The ready signal works as it's emitted immediately after NOTIFICATION_READY. - - if (enabler[ENABLER_PARENT_PHYSICS_PROCESS] && get_parent()) { - get_parent()->connect(SceneStringNames::get_singleton()->ready, - callable_mp(get_parent(), &Node::set_physics_process), varray(false), CONNECT_ONESHOT); - } - if (enabler[ENABLER_PARENT_PROCESS] && get_parent()) { - get_parent()->connect(SceneStringNames::get_singleton()->ready, - callable_mp(get_parent(), &Node::set_process), varray(false), CONNECT_ONESHOT); - } - } - - 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 { - String warning = VisibilityNotifier2D::get_configuration_warning(); - -#ifdef TOOLS_ENABLED - if (is_inside_tree() && get_parent() && (get_parent()->get_filename() == String() && get_parent() != get_tree()->get_edited_scene_root())) { - if (!warning.is_empty()) { - warning += "\n\n"; - } - warning += TTR("VisibilityEnabler2D works best when used with the edited scene root directly as parent."); - } -#endif - return warning; -} - -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; -} 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 3d1701a1e5..9c236a138f 100644 --- a/scene/2d/visibility_notifier_2d.h +++ b/scene/2d/visible_on_screen_notifier_2d.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* visibility_notifier_2d.h */ +/* visible_on_screen_notifier_2d.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,25 +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() {} @@ -64,49 +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: + ObjectID node_id; virtual void _screen_enter() override; virtual void _screen_exit() override; - bool visible = false; - - 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 override; + 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 |