diff options
124 files changed, 3318 insertions, 1036 deletions
diff --git a/core/core_bind.cpp b/core/core_bind.cpp index 9a58528bd7..8ec934db62 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -196,6 +196,10 @@ int _OS::get_low_processor_usage_mode_sleep_usec() const { return OS::get_singleton()->get_low_processor_usage_mode_sleep_usec(); } +void _OS::alert(const String &p_alert, const String &p_title) { + OS::get_singleton()->alert(p_alert, p_title); +} + String _OS::get_executable_path() const { return OS::get_singleton()->get_executable_path(); } @@ -487,6 +491,8 @@ void _OS::_bind_methods() { ClassDB::bind_method(D_METHOD("open_midi_inputs"), &_OS::open_midi_inputs); ClassDB::bind_method(D_METHOD("close_midi_inputs"), &_OS::close_midi_inputs); + ClassDB::bind_method(D_METHOD("alert", "text", "title"), &_OS::alert, DEFVAL("Alert!")); + ClassDB::bind_method(D_METHOD("set_low_processor_usage_mode", "enable"), &_OS::set_low_processor_usage_mode); ClassDB::bind_method(D_METHOD("is_in_low_processor_usage_mode"), &_OS::is_in_low_processor_usage_mode); diff --git a/core/core_bind.h b/core/core_bind.h index 673dbe32c4..43f74dc9bd 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -162,6 +162,8 @@ public: void set_low_processor_usage_mode_sleep_usec(int p_usec); int get_low_processor_usage_mode_sleep_usec() const; + void alert(const String &p_alert, const String &p_title = "ALERT!"); + String get_executable_path() const; int execute(const String &p_path, const Vector<String> &p_arguments, Array r_output = Array(), bool p_read_stderr = false); int create_process(const String &p_path, const Vector<String> &p_arguments); diff --git a/core/io/resource.cpp b/core/io/resource.cpp index efa622d976..91a1386965 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -33,6 +33,7 @@ #include "core/core_string_names.h" #include "core/io/file_access.h" #include "core/io/resource_loader.h" +#include "core/math/math_funcs.h" #include "core/object/script_language.h" #include "core/os/os.h" #include "scene/main/node.h" //only so casting works @@ -94,12 +95,43 @@ String Resource::get_path() const { return path_cache; } -void Resource::set_subindex(int p_sub_index) { - subindex = p_sub_index; +String Resource::generate_scene_unique_id() { + // Generate a unique enough hash, but still user-readable. + // If it's not unique it does not matter because the saver will try again. + OS::Date date = OS::get_singleton()->get_date(); + OS::Time time = OS::get_singleton()->get_time(); + uint32_t hash = hash_djb2_one_32(OS::get_singleton()->get_ticks_usec()); + hash = hash_djb2_one_32(date.year, hash); + hash = hash_djb2_one_32(date.month, hash); + hash = hash_djb2_one_32(date.day, hash); + hash = hash_djb2_one_32(time.hour, hash); + hash = hash_djb2_one_32(time.minute, hash); + hash = hash_djb2_one_32(time.second, hash); + hash = hash_djb2_one_32(Math::rand(), hash); + + static constexpr uint32_t characters = 5; + static constexpr uint32_t char_count = ('z' - 'a'); + static constexpr uint32_t base = char_count + ('9' - '0'); + String id; + for (uint32_t i = 0; i < characters; i++) { + uint32_t c = hash % base; + if (c < char_count) { + id += String::chr('a' + c); + } else { + id += String::chr('0' + (c - char_count)); + } + hash /= base; + } + + return id; +} + +void Resource::set_scene_unique_id(const String &p_id) { + scene_unique_id = p_id; } -int Resource::get_subindex() const { - return subindex; +String Resource::get_scene_unique_id() const { + return scene_unique_id; } void Resource::set_name(const String &p_name) { @@ -350,8 +382,8 @@ bool Resource::is_translation_remapped() const { #ifdef TOOLS_ENABLED //helps keep IDs same number when loading/saving scenes. -1 clears ID and it Returns -1 when no id stored -void Resource::set_id_for_path(const String &p_path, int p_id) { - if (p_id == -1) { +void Resource::set_id_for_path(const String &p_path, const String &p_id) { + if (p_id == "") { ResourceCache::path_cache_lock.write_lock(); ResourceCache::resource_path_cache[p_path].erase(get_path()); ResourceCache::path_cache_lock.write_unlock(); @@ -362,15 +394,15 @@ void Resource::set_id_for_path(const String &p_path, int p_id) { } } -int Resource::get_id_for_path(const String &p_path) const { +String Resource::get_id_for_path(const String &p_path) const { ResourceCache::path_cache_lock.read_lock(); if (ResourceCache::resource_path_cache[p_path].has(get_path())) { - int result = ResourceCache::resource_path_cache[p_path][get_path()]; + String result = ResourceCache::resource_path_cache[p_path][get_path()]; ResourceCache::path_cache_lock.read_unlock(); return result; } else { ResourceCache::path_cache_lock.read_unlock(); - return -1; + return ""; } } #endif @@ -414,7 +446,7 @@ Resource::~Resource() { HashMap<String, Resource *> ResourceCache::resources; #ifdef TOOLS_ENABLED -HashMap<String, HashMap<String, int>> ResourceCache::resource_path_cache; +HashMap<String, HashMap<String, String>> ResourceCache::resource_path_cache; #endif RWLock ResourceCache::lock; diff --git a/core/io/resource.h b/core/io/resource.h index 028fed1c6e..0f88738f76 100644 --- a/core/io/resource.h +++ b/core/io/resource.h @@ -59,7 +59,7 @@ private: String name; String path_cache; - int subindex = 0; + String scene_unique_id; virtual bool _use_builtin_script() const { return true; } @@ -105,8 +105,9 @@ public: virtual void set_path(const String &p_path, bool p_take_over = false); String get_path() const; - void set_subindex(int p_sub_index); - int get_subindex() const; + static String generate_scene_unique_id(); + void set_scene_unique_id(const String &p_id); + String get_scene_unique_id() const; virtual Ref<Resource> duplicate(bool p_subresources = false) const; Ref<Resource> duplicate_for_local_scene(Node *p_for_scene, Map<Ref<Resource>, Ref<Resource>> &remap_cache); @@ -140,8 +141,8 @@ public: #ifdef TOOLS_ENABLED //helps keep IDs same number when loading/saving scenes. -1 clears ID and it Returns -1 when no id stored - void set_id_for_path(const String &p_path, int p_id); - int get_id_for_path(const String &p_path) const; + void set_id_for_path(const String &p_path, const String &p_id); + String get_id_for_path(const String &p_path) const; #endif Resource(); @@ -156,7 +157,7 @@ class ResourceCache { static RWLock lock; static HashMap<String, Resource *> resources; #ifdef TOOLS_ENABLED - static HashMap<String, HashMap<String, int>> resource_path_cache; // each tscn has a set of resource paths and IDs + static HashMap<String, HashMap<String, String>> resource_path_cache; // Each tscn has a set of resource paths and IDs. static RWLock path_cache_lock; #endif // TOOLS_ENABLED friend void unregister_core_types(); diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index 0e9815245f..e889586afc 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -84,9 +84,10 @@ enum { OBJECT_EXTERNAL_RESOURCE = 1, OBJECT_INTERNAL_RESOURCE = 2, OBJECT_EXTERNAL_RESOURCE_INDEX = 3, - //version 2: added 64 bits support for float and int - //version 3: changed nodepath encoding - FORMAT_VERSION = 3, + // Version 2: added 64 bits support for float and int. + // Version 3: changed nodepath encoding. + // Version 4: new string ID for ext/subresources, breaks forward compat. + FORMAT_VERSION = 4, FORMAT_VERSION_CAN_RENAME_DEPS = 1, FORMAT_VERSION_NO_NODEPATH_PROPERTY = 3, }; @@ -311,7 +312,14 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) { } break; case OBJECT_INTERNAL_RESOURCE: { uint32_t index = f->get_32(); - String path = res_path + "::" + itos(index); + String path; + + if (using_named_scene_ids) { // New format. + ERR_FAIL_INDEX_V((int)index, internal_resources.size(), ERR_PARSE_ERROR); + path = internal_resources[index].path; + } else { + path += res_path + "::" + itos(index); + } //always use internal cache for loading internal resources if (!internal_index_cache.has(path)) { @@ -320,7 +328,6 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) { } else { r_v = internal_index_cache[path]; } - } break; case OBJECT_EXTERNAL_RESOURCE: { //old file format, still around for compatibility @@ -378,7 +385,6 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) { ERR_FAIL_V(ERR_FILE_CORRUPT); } break; } - } break; case VARIANT_CALLABLE: { r_v = Callable(); @@ -659,15 +665,17 @@ Error ResourceLoaderBinary::load() { //maybe it is loaded already String path; - int subindex = 0; + String id; if (!main) { path = internal_resources[i].path; if (path.begins_with("local://")) { path = path.replace_first("local://", ""); - subindex = path.to_int(); + id = path; path = res_path + "::" + path; + + internal_resources.write[i].path = path; // Update path. } if (cache_mode == ResourceFormatLoader::CACHE_MODE_REUSE) { @@ -722,7 +730,7 @@ Error ResourceLoaderBinary::load() { if (path != String() && cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { r->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE); //if got here because the resource with same path has different type, replace it } - r->set_subindex(subindex); + r->set_scene_unique_id(id); } if (!main) { @@ -879,7 +887,11 @@ void ResourceLoaderBinary::open(FileAccess *p_f) { print_bl("type: " + type); importmd_ofs = f->get_64(); - for (int i = 0; i < 14; i++) { + uint32_t flags = f->get_32(); + if (flags & ResourceFormatSaverBinaryInstance::FORMAT_FLAG_NAMED_SCENE_IDS) { + using_named_scene_ids = true; + } + for (int i = 0; i < 13; i++) { f->get_32(); //skip a few reserved fields } @@ -1269,11 +1281,7 @@ void ResourceFormatSaverBinaryInstance::_pad_buffer(FileAccess *f, int p_bytes) } } -void ResourceFormatSaverBinaryInstance::_write_variant(const Variant &p_property, const PropertyInfo &p_hint) { - write_variant(f, p_property, resource_set, external_resources, string_map, p_hint); -} - -void ResourceFormatSaverBinaryInstance::write_variant(FileAccess *f, const Variant &p_property, Set<RES> &resource_set, Map<RES, int> &external_resources, Map<StringName, int> &string_map, const PropertyInfo &p_hint) { +void ResourceFormatSaverBinaryInstance::write_variant(FileAccess *f, const Variant &p_property, Map<RES, int> &resource_map, Map<RES, int> &external_resources, Map<StringName, int> &string_map, const PropertyInfo &p_hint) { switch (p_property.get_type()) { case Variant::NIL: { f->store_32(VARIANT_NIL); @@ -1492,13 +1500,13 @@ void ResourceFormatSaverBinaryInstance::write_variant(FileAccess *f, const Varia f->store_32(OBJECT_EXTERNAL_RESOURCE_INDEX); f->store_32(external_resources[res]); } else { - if (!resource_set.has(res)) { + if (!resource_map.has(res)) { f->store_32(OBJECT_EMPTY); ERR_FAIL_MSG("Resource was not pre cached for the resource section, most likely due to circular reference."); } f->store_32(OBJECT_INTERNAL_RESOURCE); - f->store_32(res->get_subindex()); + f->store_32(resource_map[res]); //internal resource } @@ -1526,8 +1534,8 @@ void ResourceFormatSaverBinaryInstance::write_variant(FileAccess *f, const Varia continue; */ - write_variant(f, E->get(), resource_set, external_resources, string_map); - write_variant(f, d[E->get()], resource_set, external_resources, string_map); + write_variant(f, E->get(), resource_map, external_resources, string_map); + write_variant(f, d[E->get()], resource_map, external_resources, string_map); } } break; @@ -1536,7 +1544,7 @@ void ResourceFormatSaverBinaryInstance::write_variant(FileAccess *f, const Varia Array a = p_property; f->store_32(uint32_t(a.size())); for (int i = 0; i < a.size(); i++) { - write_variant(f, a[i], resource_set, external_resources, string_map); + write_variant(f, a[i], resource_map, external_resources, string_map); } } break; @@ -1816,7 +1824,8 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const RES &p save_unicode_string(f, p_resource->get_class()); f->store_64(0); //offset to import metadata - for (int i = 0; i < 14; i++) { + f->store_32(FORMAT_FLAG_NAMED_SCENE_IDS); + for (int i = 0; i < 13; i++) { f->store_32(0); // reserved } @@ -1886,37 +1895,43 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const RES &p // save internal resource table f->store_32(saved_resources.size()); //amount of internal resources Vector<uint64_t> ofs_pos; - Set<int> used_indices; + Set<String> used_unique_ids; for (List<RES>::Element *E = saved_resources.front(); E; E = E->next()) { RES r = E->get(); if (r->get_path() == "" || r->get_path().find("::") != -1) { - if (r->get_subindex() != 0) { - if (used_indices.has(r->get_subindex())) { - r->set_subindex(0); //repeated + if (r->get_scene_unique_id() != "") { + if (used_unique_ids.has(r->get_scene_unique_id())) { + r->set_scene_unique_id(""); } else { - used_indices.insert(r->get_subindex()); + used_unique_ids.insert(r->get_scene_unique_id()); } } } } + Map<RES, int> resource_map; + int res_index = 0; for (List<RES>::Element *E = saved_resources.front(); E; E = E->next()) { RES r = E->get(); if (r->get_path() == "" || r->get_path().find("::") != -1) { - if (r->get_subindex() == 0) { - int new_subindex = 1; - if (used_indices.size()) { - new_subindex = used_indices.back()->get() + 1; + if (r->get_scene_unique_id() == "") { + String new_id; + + while (true) { + new_id = r->get_class() + "_" + Resource::generate_scene_unique_id(); + if (!used_unique_ids.has(new_id)) { + break; + } } - r->set_subindex(new_subindex); - used_indices.insert(new_subindex); + r->set_scene_unique_id(new_id); + used_unique_ids.insert(new_id); } - save_unicode_string(f, "local://" + itos(r->get_subindex())); + save_unicode_string(f, "local://" + r->get_scene_unique_id()); if (takeover_paths) { - r->set_path(p_path + "::" + itos(r->get_subindex()), true); + r->set_path(p_path + "::" + r->get_scene_unique_id(), true); } #ifdef TOOLS_ENABLED r->set_edited(false); @@ -1926,6 +1941,7 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const RES &p } ofs_pos.push_back(f->get_position()); f->store_64(0); //offset in 64 bits + resource_map[r] = res_index++; } Vector<uint64_t> ofs_table; @@ -1941,7 +1957,7 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const RES &p for (List<Property>::Element *F = rd.properties.front(); F; F = F->next()) { Property &p = F->get(); f->store_32(p.name_idx); - _write_variant(p.value, F->get().pi); + write_variant(f, p.value, resource_map, external_resources, string_map, F->get().pi); } } diff --git a/core/io/resource_format_binary.h b/core/io/resource_format_binary.h index abc7403935..e3dcf44492 100644 --- a/core/io/resource_format_binary.h +++ b/core/io/resource_format_binary.h @@ -60,6 +60,7 @@ class ResourceLoaderBinary { RES cache; }; + bool using_named_scene_ids = false; bool use_sub_threads = false; float *progress = nullptr; Vector<ExtResource> external_resources; @@ -150,14 +151,16 @@ class ResourceFormatSaverBinaryInstance { }; static void _pad_buffer(FileAccess *f, int p_bytes); - void _write_variant(const Variant &p_property, const PropertyInfo &p_hint = PropertyInfo()); void _find_resources(const Variant &p_variant, bool p_main = false); static void save_unicode_string(FileAccess *f, const String &p_string, bool p_bit_on_len = false); int get_string_index(const String &p_string); public: + enum { + FORMAT_FLAG_NAMED_SCENE_IDS = 1 + }; Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0); - static void write_variant(FileAccess *f, const Variant &p_property, Set<RES> &resource_set, Map<RES, int> &external_resources, Map<StringName, int> &string_map, const PropertyInfo &p_hint = PropertyInfo()); + static void write_variant(FileAccess *f, const Variant &p_property, Map<RES, int> &resource_map, Map<RES, int> &external_resources, Map<StringName, int> &string_map, const PropertyInfo &p_hint = PropertyInfo()); }; class ResourceFormatSaverBinary : public ResourceFormatSaver { diff --git a/core/io/resource_importer.cpp b/core/io/resource_importer.cpp index b503655edd..f612b84404 100644 --- a/core/io/resource_importer.cpp +++ b/core/io/resource_importer.cpp @@ -445,3 +445,8 @@ ResourceFormatImporter *ResourceFormatImporter::singleton = nullptr; ResourceFormatImporter::ResourceFormatImporter() { singleton = this; } + +void ResourceImporter::_bind_methods() { + BIND_ENUM_CONSTANT(IMPORT_ORDER_DEFAULT); + BIND_ENUM_CONSTANT(IMPORT_ORDER_SCENE); +} diff --git a/core/io/resource_importer.h b/core/io/resource_importer.h index 2ceeb176e5..62cb608a42 100644 --- a/core/io/resource_importer.h +++ b/core/io/resource_importer.h @@ -96,6 +96,9 @@ public: class ResourceImporter : public RefCounted { GDCLASS(ResourceImporter, RefCounted); +protected: + static void _bind_methods(); + public: virtual String get_importer_name() const = 0; virtual String get_visible_name() const = 0; @@ -103,7 +106,7 @@ public: virtual String get_save_extension() const = 0; virtual String get_resource_type() const = 0; virtual float get_priority() const { return 1.0; } - virtual int get_import_order() const { return 0; } + virtual int get_import_order() const { return IMPORT_ORDER_DEFAULT; } virtual int get_format_version() const { return 0; } struct ImportOption { @@ -117,6 +120,11 @@ public: ImportOption() {} }; + enum ImportOrder { + IMPORT_ORDER_DEFAULT = 0, + IMPORT_ORDER_SCENE = 100, + }; + virtual bool has_advanced_options() const { return false; } virtual void show_advanced_options(const String &p_path) {} @@ -137,4 +145,6 @@ public: virtual String get_import_settings_string() const { return String(); } }; +VARIANT_ENUM_CAST(ResourceImporter::ImportOrder); + #endif // RESOURCE_IMPORTER_H diff --git a/core/math/vector2.h b/core/math/vector2.h index 78deb473b4..4d9f3126e9 100644 --- a/core/math/vector2.h +++ b/core/math/vector2.h @@ -300,6 +300,14 @@ struct Vector2i { return p_idx ? y : x; } + _FORCE_INLINE_ int min_axis() const { + return x < y ? 0 : 1; + } + + _FORCE_INLINE_ int max_axis() const { + return x < y ? 1 : 0; + } + Vector2i min(const Vector2i &p_vector2i) const { return Vector2(MIN(x, p_vector2i.x), MIN(y, p_vector2i.y)); } diff --git a/core/object/object.cpp b/core/object/object.cpp index 0644012318..66a4d17b7b 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -922,11 +922,11 @@ Variant Object::get_script() const { return script; } -bool Object::has_meta(const String &p_name) const { +bool Object::has_meta(const StringName &p_name) const { return metadata.has(p_name); } -void Object::set_meta(const String &p_name, const Variant &p_value) { +void Object::set_meta(const StringName &p_name, const Variant &p_value) { if (p_value.get_type() == Variant::NIL) { metadata.erase(p_name); return; @@ -935,12 +935,12 @@ void Object::set_meta(const String &p_name, const Variant &p_value) { metadata[p_name] = p_value; } -Variant Object::get_meta(const String &p_name) const { +Variant Object::get_meta(const StringName &p_name) const { ERR_FAIL_COND_V_MSG(!metadata.has(p_name), Variant(), "The object does not have any 'meta' values with the key '" + p_name + "'."); return metadata[p_name]; } -void Object::remove_meta(const String &p_name) { +void Object::remove_meta(const StringName &p_name) { metadata.erase(p_name); } @@ -964,8 +964,8 @@ Array Object::_get_method_list_bind() const { return ret; } -Vector<String> Object::_get_meta_list_bind() const { - Vector<String> _metaret; +Vector<StringName> Object::_get_meta_list_bind() const { + Vector<StringName> _metaret; List<Variant> keys; metadata.get_key_list(&keys); @@ -976,7 +976,7 @@ Vector<String> Object::_get_meta_list_bind() const { return _metaret; } -void Object::get_meta_list(List<String> *p_list) const { +void Object::get_meta_list(List<StringName> *p_list) const { List<Variant> keys; metadata.get_key_list(&keys); for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { diff --git a/core/object/object.h b/core/object/object.h index 296e07983d..b7d0916a54 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -615,7 +615,7 @@ protected: return &_class_name; } - Vector<String> _get_meta_list_bind() const; + Vector<StringName> _get_meta_list_bind() const; Array _get_property_list_bind() const; Array _get_method_list_bind() const; @@ -743,11 +743,11 @@ public: /* SCRIPT */ - bool has_meta(const String &p_name) const; - void set_meta(const String &p_name, const Variant &p_value); - void remove_meta(const String &p_name); - Variant get_meta(const String &p_name) const; - void get_meta_list(List<String> *p_list) const; + bool has_meta(const StringName &p_name) const; + void set_meta(const StringName &p_name, const Variant &p_value); + void remove_meta(const StringName &p_name); + Variant get_meta(const StringName &p_name) const; + void get_meta_list(List<StringName> *p_list) const; #ifdef TOOLS_ENABLED void set_edited(bool p_edited); diff --git a/core/object/undo_redo.cpp b/core/object/undo_redo.cpp index 0532b2ae40..6808d7602d 100644 --- a/core/object/undo_redo.cpp +++ b/core/object/undo_redo.cpp @@ -41,9 +41,13 @@ void UndoRedo::_discard_redo() { for (int i = current_action + 1; i < actions.size(); i++) { for (List<Operation>::Element *E = actions.write[i].do_ops.front(); E; E = E->next()) { if (E->get().type == Operation::TYPE_REFERENCE) { - Object *obj = ObjectDB::get_instance(E->get().object); - if (obj) { - memdelete(obj); + if (E->get().ref.is_valid()) { + E->get().ref.unref(); + } else { + Object *obj = ObjectDB::get_instance(E->get().object); + if (obj) { + memdelete(obj); + } } } } @@ -242,9 +246,13 @@ void UndoRedo::_pop_history_tail() { for (List<Operation>::Element *E = actions.write[0].undo_ops.front(); E; E = E->next()) { if (E->get().type == Operation::TYPE_REFERENCE) { - Object *obj = ObjectDB::get_instance(E->get().object); - if (obj) { - memdelete(obj); + if (E->get().ref.is_valid()) { + E->get().ref.unref(); + } else { + Object *obj = ObjectDB::get_instance(E->get().object); + if (obj) { + memdelete(obj); + } } } } diff --git a/core/os/os.cpp b/core/os/os.cpp index 535eee4797..f7af74da3e 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -110,6 +110,10 @@ void OS::printerr(const char *p_format, ...) { va_end(argp); } +void OS::alert(const String &p_alert, const String &p_title) { + fprintf(stderr, "%s: %s\n", p_title.utf8().get_data(), p_alert.utf8().get_data()); +} + void OS::set_low_processor_usage_mode(bool p_enabled) { low_processor_usage_mode = p_enabled; } diff --git a/core/os/os.h b/core/os/os.h index 301718a8b3..fcb195afe1 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -120,6 +120,8 @@ public: virtual void open_midi_inputs(); virtual void close_midi_inputs(); + virtual void alert(const String &p_alert, const String &p_title = "ALERT!"); + virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false) { return ERR_UNAVAILABLE; } virtual Error close_dynamic_library(void *p_library_handle) { return ERR_UNAVAILABLE; } virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false) { return ERR_UNAVAILABLE; } diff --git a/core/string/node_path.cpp b/core/string/node_path.cpp index d3afa7b4dd..5fae13779e 100644 --- a/core/string/node_path.cpp +++ b/core/string/node_path.cpp @@ -240,19 +240,26 @@ NodePath NodePath::rel_path_to(const NodePath &p_np) const { common_parent--; Vector<StringName> relpath; + relpath.resize(src_dirs.size() + dst_dirs.size() + 1); - for (int i = src_dirs.size() - 1; i > common_parent; i--) { - relpath.push_back(".."); + StringName *relpath_ptr = relpath.ptrw(); + + int path_size = 0; + StringName back_str(".."); + for (int i = common_parent + 1; i < src_dirs.size(); i++) { + relpath_ptr[path_size++] = back_str; } for (int i = common_parent + 1; i < dst_dirs.size(); i++) { - relpath.push_back(dst_dirs[i]); + relpath_ptr[path_size++] = dst_dirs[i]; } - if (relpath.size() == 0) { - relpath.push_back("."); + if (path_size == 0) { + relpath_ptr[path_size++] = "."; } + relpath.resize(path_size); + return NodePath(relpath, p_np.get_subnames(), false); } diff --git a/doc/classes/AStar.xml b/doc/classes/AStar.xml index cc7f7072b9..327785a3b6 100644 --- a/doc/classes/AStar.xml +++ b/doc/classes/AStar.xml @@ -33,7 +33,7 @@ [/csharp] [/codeblocks] [method _estimate_cost] should return a lower bound of the distance, i.e. [code]_estimate_cost(u, v) <= _compute_cost(u, v)[/code]. This serves as a hint to the algorithm because the custom [code]_compute_cost[/code] might be computation-heavy. If this is not the case, make [method _estimate_cost] return the same value as [method _compute_cost] to provide the algorithm with the most accurate information. - If the default [method _estimate_cost] and [method _compute_cost] methods are used, or if the supplied [method _estimate_cost] method returns a lower bound of the cost, then the paths returned by A* will be the lowest cost paths. Here, the cost of a path equals to the sum of the [method _compute_cost] results of all segments in the path multiplied by the [code]weight_scale[/code]s of the end points of the respective segments. If the default methods are used and the [code]weight_scale[/code]s of all points are set to [code]1.0[/code], then this equals to the sum of Euclidean distances of all segments in the path. + If the default [method _estimate_cost] and [method _compute_cost] methods are used, or if the supplied [method _estimate_cost] method returns a lower bound of the cost, then the paths returned by A* will be the lowest-cost paths. Here, the cost of a path equals the sum of the [method _compute_cost] results of all segments in the path multiplied by the [code]weight_scale[/code]s of the endpoints of the respective segments. If the default methods are used and the [code]weight_scale[/code]s of all points are set to [code]1.0[/code], then this equals the sum of Euclidean distances of all segments in the path. </description> <tutorials> </tutorials> diff --git a/doc/classes/Animation.xml b/doc/classes/Animation.xml index 894e784397..4d192f8e97 100644 --- a/doc/classes/Animation.xml +++ b/doc/classes/Animation.xml @@ -616,7 +616,7 @@ <argument index="1" name="path" type="NodePath"> </argument> <description> - Sets the path of a track. Paths must be valid scene-tree paths to a node, and must be specified starting from the parent node of the node that will reproduce the animation. Tracks that control properties or bones must append their name after the path, separated by [code]":"[/code]. + Sets the path of a track. Paths must be valid scene-tree paths to a node and must be specified starting from the parent node of the node that will reproduce the animation. Tracks that control properties or bones must append their name after the path, separated by [code]":"[/code]. For example, [code]"character/skeleton:ankle"[/code] or [code]"character/mesh:transform/local"[/code]. </description> </method> diff --git a/doc/classes/AnimationTree.xml b/doc/classes/AnimationTree.xml index af7d8d73e8..63460b70c5 100644 --- a/doc/classes/AnimationTree.xml +++ b/doc/classes/AnimationTree.xml @@ -7,7 +7,7 @@ Note: When linked with an [AnimationPlayer], several properties and methods of the corresponding [AnimationPlayer] will not function as expected. Playback and transitions should be handled using only the [AnimationTree] and its constituent [AnimationNode](s). The [AnimationPlayer] node should be used solely for adding, deleting, and editing animations. </description> <tutorials> - <link title="AnimationTree">https://docs.godotengine.org/en/latest/tutorials/animation/animation_tree.html</link> + <link title="Using AnimationTree">https://docs.godotengine.org/en/latest/tutorials/animation/animation_tree.html</link> <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> </tutorials> <methods> @@ -24,7 +24,7 @@ <return type="Transform3D"> </return> <description> - Retrieve the motion of the [member root_motion_track] as a [Transform3D] that can be used elsewhere. If [member root_motion_track] is not a path to a track of type [constant Animation.TYPE_TRANSFORM3D], returns an identity transformation. + Retrieve the motion of the [member root_motion_track] as a [Transform3D] that can be used elsewhere. If [member root_motion_track] is not a path to a track of type [constant Animation.TYPE_TRANSFORM3D], returns an identity transformation. See also [member root_motion_track] and [RootMotionView]. </description> </method> <method name="rename_parameter"> @@ -50,7 +50,7 @@ </member> <member name="root_motion_track" type="NodePath" setter="set_root_motion_track" getter="get_root_motion_track" default="NodePath("")"> The path to the Animation track used for root motion. Paths must be valid scene-tree paths to a node, and must be specified starting from the parent node of the node that will reproduce the animation. To specify a track that controls properties or bones, append its name after the path, separated by [code]":"[/code]. For example, [code]"character/skeleton:ankle"[/code] or [code]"character/mesh:transform/local"[/code]. - If the track has type [constant Animation.TYPE_TRANSFORM3D], the transformation will be cancelled visually, and the animation will appear to stay in place. + If the track has type [constant Animation.TYPE_TRANSFORM3D], the transformation will be cancelled visually, and the animation will appear to stay in place. See also [method get_root_motion_transform] and [RootMotionView]. </member> <member name="tree_root" type="AnimationNode" setter="set_tree_root" getter="get_tree_root"> The root animation node of this [AnimationTree]. See [AnimationNode]. diff --git a/doc/classes/Area2D.xml b/doc/classes/Area2D.xml index f52c810ad2..cf2e7044af 100644 --- a/doc/classes/Area2D.xml +++ b/doc/classes/Area2D.xml @@ -34,7 +34,7 @@ </argument> <description> If [code]true[/code], the given area overlaps the Area2D. - [b]Note:[/b] The result of this test is not immediate after moving objects. For performance, list of overlaps is updated once per frame and before the physics step. Consider using signals instead. + [b]Note:[/b] The result of this test is not immediate after moving objects. For performance, the list of overlaps is updated once per frame and before the physics step. Consider using signals instead. </description> </method> <method name="overlaps_body" qualifiers="const"> @@ -45,7 +45,7 @@ <description> If [code]true[/code], the given physics body overlaps the Area2D. [b]Note:[/b] The result of this test is not immediate after moving objects. For performance, list of overlaps is updated once per frame and before the physics step. Consider using signals instead. - The [code]body[/code] argument can either be a [PhysicsBody2D] or a [TileMap] instance (while TileMaps are not physics body themselves, they register their tiles with collision shapes as a virtual physics body). + The [code]body[/code] argument can either be a [PhysicsBody2D] or a [TileMap] instance (while TileMaps are not physics bodies themselves, they register their tiles with collision shapes as a virtual physics body). </description> </method> </methods> diff --git a/doc/classes/Array.xml b/doc/classes/Array.xml index 543ec096c7..1ed7250adc 100644 --- a/doc/classes/Array.xml +++ b/doc/classes/Array.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="Array" version="4.0"> <brief_description> - Generic array datatype. + A generic array datatype. </brief_description> <description> - Generic array which can contain several elements of any type, accessible by a numerical index starting at 0. Negative indices can be used to count from the back, like in Python (-1 is the last element, -2 the second to last, etc.). + A generic array that can contain several elements of any type, accessible by a numerical index starting at 0. Negative indices can be used to count from the back, like in Python (-1 is the last element, -2 is the second to last, etc.). [b]Example:[/b] [codeblocks] [gdscript] @@ -39,7 +39,7 @@ [/csharp] [/codeblocks] [b]Note:[/b] Concatenating with the [code]+=[/code] operator will create a new array, which has a cost. If you want to append another array to an existing array, [method append_array] is more efficient. - [b]Note:[/b] Arrays are always passed by reference. To get a copy of an array which can be modified independently of the original array, use [method duplicate]. + [b]Note:[/b] Arrays are always passed by reference. To get a copy of an array that can be modified independently of the original array, use [method duplicate]. [b]Note:[/b] When declaring an array with [code]const[/code], the array itself can still be mutated by defining the values at individual indices or pushing/removing elements. Using [code]const[/code] will only prevent assigning the constant with another value after it was initialized. </description> <tutorials> diff --git a/doc/classes/ArrayMesh.xml b/doc/classes/ArrayMesh.xml index 7fbf53c7d1..e3d2eb74ff 100644 --- a/doc/classes/ArrayMesh.xml +++ b/doc/classes/ArrayMesh.xml @@ -75,7 +75,7 @@ </argument> <description> Creates a new surface. - Surfaces are created to be rendered using a [code]primitive[/code], which may be any of the types defined in [enum Mesh.PrimitiveType]. (As a note, when using indices, it is recommended to only use points, lines or triangles.) [method Mesh.get_surface_count] will become the [code]surf_idx[/code] for this new surface. + Surfaces are created to be rendered using a [code]primitive[/code], which may be any of the types defined in [enum Mesh.PrimitiveType]. (As a note, when using indices, it is recommended to only use points, lines, or triangles.) [method Mesh.get_surface_count] will become the [code]surf_idx[/code] for this new surface. The [code]arrays[/code] argument is an array of arrays. See [enum Mesh.ArrayType] for the values used in this array. For example, [code]arrays[0][/code] is the array of vertices. That first vertex sub-array is always required; the others are optional. Adding an index array puts this function into "index mode" where the vertex and other arrays become the sources of data and the index array defines the vertex order. All sub-arrays must have the same length as the vertex array or be empty, except for [constant Mesh.ARRAY_INDEX] if it is used. </description> </method> diff --git a/doc/classes/CPUParticles3D.xml b/doc/classes/CPUParticles3D.xml index d7a89eef11..29e04dda7e 100644 --- a/doc/classes/CPUParticles3D.xml +++ b/doc/classes/CPUParticles3D.xml @@ -177,9 +177,21 @@ <member name="emission_normals" type="PackedVector3Array" setter="set_emission_normals" getter="get_emission_normals"> Sets the direction the particles will be emitted in when using [constant EMISSION_SHAPE_DIRECTED_POINTS]. </member> - <member name="emission_points" type="PackedVector3Array" setter="set_emission_points" getter="get_emission_points" default="PackedVector3Array()"> + <member name="emission_points" type="PackedVector3Array" setter="set_emission_points" getter="get_emission_points"> Sets the initial positions to spawn particles when using [constant EMISSION_SHAPE_POINTS] or [constant EMISSION_SHAPE_DIRECTED_POINTS]. </member> + <member name="emission_ring_axis" type="Vector3" setter="set_emission_ring_axis" getter="get_emission_ring_axis"> + The axis of the ring when using the emitter [constant EMISSION_SHAPE_RING]. + </member> + <member name="emission_ring_height" type="float" setter="set_emission_ring_height" getter="get_emission_ring_height"> + The height of the ring when using the emitter [constant EMISSION_SHAPE_RING]. + </member> + <member name="emission_ring_inner_radius" type="float" setter="set_emission_ring_inner_radius" getter="get_emission_ring_inner_radius"> + The inner radius of the ring when using the emitter [constant EMISSION_SHAPE_RING]. + </member> + <member name="emission_ring_radius" type="float" setter="set_emission_ring_radius" getter="get_emission_ring_radius"> + The radius of the ring when using the emitter [constant EMISSION_SHAPE_RING]. + </member> <member name="emission_shape" type="int" setter="set_emission_shape" getter="get_emission_shape" enum="CPUParticles3D.EmissionShape" default="0"> Particles will be emitted inside this region. See [enum EmissionShape] for possible values. </member> @@ -378,7 +390,10 @@ <constant name="EMISSION_SHAPE_DIRECTED_POINTS" value="4" enum="EmissionShape"> Particles will be emitted at a position chosen randomly among [member emission_points]. Particle velocity and rotation will be set based on [member emission_normals]. Particle color will be modulated by [member emission_colors]. </constant> - <constant name="EMISSION_SHAPE_MAX" value="5" enum="EmissionShape"> + <constant name="EMISSION_SHAPE_RING" value="5" enum="EmissionShape"> + Particles will be emitted in a ring or cylinder. + </constant> + <constant name="EMISSION_SHAPE_MAX" value="6" enum="EmissionShape"> Represents the size of the [enum EmissionShape] enum. </constant> </constants> diff --git a/doc/classes/Camera2D.xml b/doc/classes/Camera2D.xml index e8f4cb49b6..ac90b6950b 100644 --- a/doc/classes/Camera2D.xml +++ b/doc/classes/Camera2D.xml @@ -5,7 +5,7 @@ </brief_description> <description> Camera node for 2D scenes. It forces the screen (current layer) to scroll following this node. This makes it easier (and faster) to program scrollable scenes than manually changing the position of [CanvasItem]-based nodes. - This node is intended to be a simple helper to get things going quickly and it may happen that more functionality is desired to change how the camera works. To make your own custom camera node, inherit from [Node2D] and change the transform of the canvas by setting [member Viewport.canvas_transform] in [Viewport] (you can obtain the current [Viewport] by using [method Node.get_viewport]). + This node is intended to be a simple helper to get things going quickly, but more functionality may be desired to change how the camera works. To make your own custom camera node, inherit it from [Node2D] and change the transform of the canvas by setting [member Viewport.canvas_transform] in [Viewport] (you can obtain the current [Viewport] by using [method Node.get_viewport]). Note that the [Camera2D] node's [code]position[/code] doesn't represent the actual position of the screen, which may differ due to applied smoothing or limits. You can use [method get_camera_screen_center] to get the real position. </description> <tutorials> diff --git a/doc/classes/Camera3D.xml b/doc/classes/Camera3D.xml index 0f0bfdc654..f74baa00a0 100644 --- a/doc/classes/Camera3D.xml +++ b/doc/classes/Camera3D.xml @@ -46,7 +46,7 @@ <return type="Array"> </return> <description> - Returns the camera's frustum planes in world-space units as an array of [Plane]s in the following order: near, far, left, top, right, bottom. Not to be confused with [member frustum_offset]. + Returns the camera's frustum planes in world space units as an array of [Plane]s in the following order: near, far, left, top, right, bottom. Not to be confused with [member frustum_offset]. </description> </method> <method name="is_position_behind" qualifiers="const"> @@ -92,7 +92,7 @@ <argument index="1" name="z_depth" type="float"> </argument> <description> - Returns the 3D point in worldspace that maps to the given 2D coordinate in the [Viewport] rectangle on a plane that is the given [code]z_depth[/code] distance into the scene away from the camera. + Returns the 3D point in world space that maps to the given 2D coordinate in the [Viewport] rectangle on a plane that is the given [code]z_depth[/code] distance into the scene away from the camera. </description> </method> <method name="project_ray_normal" qualifiers="const"> @@ -101,7 +101,7 @@ <argument index="0" name="screen_point" type="Vector2"> </argument> <description> - Returns a normal vector in worldspace, that is the result of projecting a point on the [Viewport] rectangle by the camera projection. This is useful for casting rays in the form of (origin, normal) for object intersection or picking. + Returns a normal vector in world space, that is the result of projecting a point on the [Viewport] rectangle by the camera projection. This is useful for casting rays in the form of (origin, normal) for object intersection or picking. </description> </method> <method name="project_ray_origin" qualifiers="const"> @@ -110,7 +110,7 @@ <argument index="0" name="screen_point" type="Vector2"> </argument> <description> - Returns a 3D position in worldspace, that is the result of projecting a point on the [Viewport] rectangle by the camera projection. This is useful for casting rays in the form of (origin, normal) for object intersection or picking. + Returns a 3D position in world space, that is the result of projecting a point on the [Viewport] rectangle by the camera projection. This is useful for casting rays in the form of (origin, normal) for object intersection or picking. </description> </method> <method name="set_cull_mask_bit"> @@ -136,7 +136,7 @@ <argument index="3" name="z_far" type="float"> </argument> <description> - Sets the camera projection to frustum mode (see [constant PROJECTION_FRUSTUM]), by specifying a [code]size[/code], an [code]offset[/code], and the [code]z_near[/code] and [code]z_far[/code] clip planes in world-space units. + Sets the camera projection to frustum mode (see [constant PROJECTION_FRUSTUM]), by specifying a [code]size[/code], an [code]offset[/code], and the [code]z_near[/code] and [code]z_far[/code] clip planes in world space units. </description> </method> <method name="set_orthogonal"> @@ -149,7 +149,7 @@ <argument index="2" name="z_far" type="float"> </argument> <description> - Sets the camera projection to orthogonal mode (see [constant PROJECTION_ORTHOGONAL]), by specifying a [code]size[/code], and the [code]z_near[/code] and [code]z_far[/code] clip planes in world-space units. (As a hint, 2D games often use this projection, with values specified in pixels.) + Sets the camera projection to orthogonal mode (see [constant PROJECTION_ORTHOGONAL]), by specifying a [code]size[/code], and the [code]z_near[/code] and [code]z_far[/code] clip planes in world space units. (As a hint, 2D games often use this projection, with values specified in pixels.) </description> </method> <method name="set_perspective"> @@ -162,7 +162,7 @@ <argument index="2" name="z_far" type="float"> </argument> <description> - Sets the camera projection to perspective mode (see [constant PROJECTION_PERSPECTIVE]), by specifying a [code]fov[/code] (field of view) angle in degrees, and the [code]z_near[/code] and [code]z_far[/code] clip planes in world-space units. + Sets the camera projection to perspective mode (see [constant PROJECTION_PERSPECTIVE]), by specifying a [code]fov[/code] (field of view) angle in degrees, and the [code]z_near[/code] and [code]z_far[/code] clip planes in world space units. </description> </method> <method name="unproject_position" qualifiers="const"> @@ -171,7 +171,7 @@ <argument index="0" name="world_point" type="Vector3"> </argument> <description> - Returns the 2D coordinate in the [Viewport] rectangle that maps to the given 3D point in worldspace. + Returns the 2D coordinate in the [Viewport] rectangle that maps to the given 3D point in world space. [b]Note:[/b] When using this to position GUI elements over a 3D viewport, use [method is_position_behind] to prevent them from appearing if the 3D point is behind the camera: [codeblock] # This code block is part of a script that inherits from Node3D. diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml index 23cbdd8cab..79b3788449 100644 --- a/doc/classes/Control.xml +++ b/doc/classes/Control.xml @@ -9,7 +9,7 @@ [b]User Interface nodes and input[/b] Godot sends input events to the scene's root node first, by calling [method Node._input]. [method Node._input] forwards the event down the node tree to the nodes under the mouse cursor, or on keyboard focus. To do so, it calls [code]MainLoop._input_event[/code]. [b]FIXME:[/b] No longer valid after DisplayServer split and Input refactoring. - Call [method accept_event] so no other node receives the event. Once you accepted an input, it becomes handled so [method Node._unhandled_input] will not process it. + Call [method accept_event] so no other node receives the event. Once you accept an input, it becomes handled so [method Node._unhandled_input] will not process it. Only one [Control] node can be in keyboard focus. Only the node in focus will receive keyboard events. To get the focus, call [method grab_focus]. [Control] nodes lose focus when another node grabs it, or if you hide the node in focus. Sets [member mouse_filter] to [constant MOUSE_FILTER_IGNORE] to tell a [Control] node to ignore mouse or touch events. You'll need it if you place an icon on top of a button. [Theme] resources change the Control's appearance. If you change the [Theme] on a [Control] node, it affects all of its children. To override some of the theme's parameters, call one of the [code]add_theme_*_override[/code] methods, like [method add_theme_font_override]. You can override the theme with the inspector. @@ -147,7 +147,7 @@ * control has [member mouse_filter] set to [constant MOUSE_FILTER_IGNORE]; * control is obstructed by another [Control] on top of it, which doesn't have [member mouse_filter] set to [constant MOUSE_FILTER_IGNORE]; * control's parent has [member mouse_filter] set to [constant MOUSE_FILTER_STOP] or has accepted the event; - * it happens outside parent's rectangle and the parent has either [member rect_clip_content] enabled. + * it happens outside the parent's rectangle and the parent has either [member rect_clip_content] enabled. </description> </method> <method name="_has_point" qualifiers="virtual const"> @@ -168,10 +168,10 @@ </argument> <description> Virtual method to be implemented by the user. Returns a [Control] node that should be used as a tooltip instead of the default one. The [code]for_text[/code] includes the contents of the [member hint_tooltip] property. - The returned node must be of type [Control] or Control-derived. It can have child nodes of any type. It is freed when the tooltip disappears, so make sure you always provide a new instance (if you want to use a pre-existing node from your scene tree, you can duplicate it and pass the duplicated instance).When [code]null[/code] or a non-Control node is returned, the default tooltip will be used instead. + The returned node must be of type [Control] or Control-derived. It can have child nodes of any type. It is freed when the tooltip disappears, so make sure you always provide a new instance (if you want to use a pre-existing node from your scene tree, you can duplicate it and pass the duplicated instance). When [code]null[/code] or a non-Control node is returned, the default tooltip will be used instead. The returned node will be added as child to a [PopupPanel], so you should only provide the contents of that panel. That [PopupPanel] can be themed using [method Theme.set_stylebox] for the type [code]"TooltipPanel"[/code] (see [member hint_tooltip] for an example). [b]Note:[/b] The tooltip is shrunk to minimal size. If you want to ensure it's fully visible, you might want to set its [member rect_min_size] to some non-zero value. - [b]Note:[/b] The node (and any relevant children) should be [member CanvasItem.visible] when returned, otherwise the viewport that instantiates it will not be able to calculate its minimum size reliably. + [b]Note:[/b] The node (and any relevant children) should be [member CanvasItem.visible] when returned, otherwise, the viewport that instantiates it will not be able to calculate its minimum size reliably. Example of usage with a custom-constructed node: [codeblocks] [gdscript] @@ -860,7 +860,7 @@ <argument index="1" name="keep_offsets" type="bool" default="false"> </argument> <description> - Sets the anchors to a [code]preset[/code] from [enum Control.LayoutPreset] enum. This is code equivalent of using the Layout menu in 2D editor. + Sets the anchors to a [code]preset[/code] from [enum Control.LayoutPreset] enum. This is the code equivalent to using the Layout menu in the 2D editor. If [code]keep_offsets[/code] is [code]true[/code], control's position will also be updated. </description> </method> @@ -1029,7 +1029,7 @@ <argument index="2" name="margin" type="int" default="0"> </argument> <description> - Sets the offsets to a [code]preset[/code] from [enum Control.LayoutPreset] enum. This is code equivalent of using the Layout menu in 2D editor. + Sets the offsets to a [code]preset[/code] from [enum Control.LayoutPreset] enum. This is the code equivalent to using the Layout menu in the 2D editor. Use parameter [code]resize_mode[/code] with constants from [enum Control.LayoutPresetMode] to better determine the resulting size of the [Control]. Constant size will be ignored if used with presets that change size, e.g. [code]PRESET_LEFT_WIDE[/code]. Use parameter [code]margin[/code] to determine the gap between the [Control] and the edges. </description> diff --git a/doc/classes/Directory.xml b/doc/classes/Directory.xml index dae8d83f0c..ff92f6c5ef 100644 --- a/doc/classes/Directory.xml +++ b/doc/classes/Directory.xml @@ -159,7 +159,7 @@ <argument index="1" name="show_hidden" type="bool" default="false"> </argument> <description> - Initializes the stream used to list all files and directories using the [method get_next] function, closing the current opened stream if needed. Once the stream has been processed, it should typically be closed with [method list_dir_end]. + Initializes the stream used to list all files and directories using the [method get_next] function, closing the currently opened stream if needed. Once the stream has been processed, it should typically be closed with [method list_dir_end]. If [code]show_navigational[/code] is [code]true[/code], [code].[/code] and [code]..[/code] are included too. If [code]show_hidden[/code] is [code]true[/code], hidden files are included too. </description> diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index 17b51d5cbe..5c9bcc5014 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -7,16 +7,6 @@ <tutorials> </tutorials> <methods> - <method name="alert"> - <return type="void"> - </return> - <argument index="0" name="text" type="String"> - </argument> - <argument index="1" name="title" type="String" default=""Alert!""> - </argument> - <description> - </description> - </method> <method name="clipboard_get" qualifiers="const"> <return type="String"> </return> diff --git a/doc/classes/EditorImportPlugin.xml b/doc/classes/EditorImportPlugin.xml index a532e9bc2b..1722b278f8 100644 --- a/doc/classes/EditorImportPlugin.xml +++ b/doc/classes/EditorImportPlugin.xml @@ -126,7 +126,7 @@ <return type="int"> </return> <description> - Gets the order of this importer to be run when importing resources. Higher values will be called later. Use this to ensure the importer runs after the dependencies are already imported. + Gets the order of this importer to be run when importing resources. Importers with [i]lower[/i] import orders will be called first, and higher values will be called later. Use this to ensure the importer runs after the dependencies are already imported. The default import order is [code]0[/code] unless overridden by a specific importer. See [enum ResourceImporter.ImportOrder] for some predefined values. </description> </method> <method name="_get_importer_name" qualifiers="virtual"> diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml index 753227513b..efd1d30565 100644 --- a/doc/classes/EditorPlugin.xml +++ b/doc/classes/EditorPlugin.xml @@ -404,7 +404,7 @@ </argument> <description> Adds a custom type, which will appear in the list of nodes or resources. An icon can be optionally passed. - When given node or resource is selected, the base type will be instantiated (e.g. "Node3D", "Control", "Resource"), then the script will be loaded and set to this object. + When a given node or resource is selected, the base type will be instantiated (e.g. "Node3D", "Control", "Resource"), then the script will be loaded and set to this object. You can use the virtual method [method _handles] to check if your custom object is being edited by checking the script or using the [code]is[/code] keyword. During run-time, this will be a simple object with a script so this function does not need to be called then. </description> diff --git a/doc/classes/Engine.xml b/doc/classes/Engine.xml index c0a8407ece..3fa7894fdc 100644 --- a/doc/classes/Engine.xml +++ b/doc/classes/Engine.xml @@ -170,7 +170,7 @@ The number of fixed iterations per second. This controls how often physics simulation and [method Node._physics_process] methods are run. This value should generally always be set to [code]60[/code] or above, as Godot doesn't interpolate the physics step. As a result, values lower than [code]60[/code] will look stuttery. This value can be increased to make input more reactive or work around tunneling issues, but keep in mind doing so will increase CPU usage. </member> <member name="physics_jitter_fix" type="float" setter="set_physics_jitter_fix" getter="get_physics_jitter_fix" default="0.5"> - Controls how much physics ticks are synchronized with real time. For 0 or less, the ticks are synchronized. Such values are recommended for network games, where clock synchronization matters. Higher values cause higher deviation of in-game clock and real clock, but allows smoothing out framerate jitters. The default value of 0.5 should be fine for most; values above 2 could cause the game to react to dropped frames with a noticeable delay and are not recommended. + Controls how much physics ticks are synchronized with real time. For 0 or less, the ticks are synchronized. Such values are recommended for network games, where clock synchronization matters. Higher values cause higher deviation of the in-game clock and real clock but smooth out framerate jitters. The default value of 0.5 should be fine for most; values above 2 could cause the game to react to dropped frames with a noticeable delay and are not recommended. [b]Note:[/b] For best results, when using a custom physics interpolation solution, the physics jitter fix should be disabled by setting [member physics_jitter_fix] to [code]0[/code]. </member> <member name="print_error_messages" type="bool" setter="set_print_error_messages" getter="is_printing_error_messages" default="true"> diff --git a/doc/classes/Image.xml b/doc/classes/Image.xml index 167b90ea73..fd80841a63 100644 --- a/doc/classes/Image.xml +++ b/doc/classes/Image.xml @@ -241,7 +241,7 @@ <argument index="0" name="renormalize" type="bool" default="false"> </argument> <description> - Generates mipmaps for the image. Mipmaps are precalculated and lower resolution copies of the image. Mipmaps are automatically used if the image needs to be scaled down when rendered. This improves image quality and the performance of the rendering. Returns an error if the image is compressed, in a custom format or if the image's width/height is 0. + Generates mipmaps for the image. Mipmaps are precalculated lower-resolution copies of the image that are automatically used if the image needs to be scaled down when rendered. They help improve image quality and performance when rendering. This method returns an error if the image is compressed, in a custom format, or if the image's width/height is [code]0[/code]. </description> </method> <method name="get_data" qualifiers="const"> @@ -710,7 +710,7 @@ </constant> <constant name="INTERPOLATE_TRILINEAR" value="3" enum="Interpolation"> Performs bilinear separately on the two most-suited mipmap levels, then linearly interpolates between them. - It's slower than [constant INTERPOLATE_BILINEAR], but produces higher-quality results with much less aliasing artifacts. + It's slower than [constant INTERPOLATE_BILINEAR], but produces higher-quality results with far fewer aliasing artifacts. If the image does not have mipmaps, they will be generated and used internally, but no mipmaps will be generated on the resulting image. [b]Note:[/b] If you intend to scale multiple copies of the original image, it's better to call [method generate_mipmaps]] on it in advance, to avoid wasting processing power in generating them again and again. On the other hand, if the image already has mipmaps, they will be used, and a new set will be generated for the resulting image. diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml index b970070659..c340895130 100644 --- a/doc/classes/Input.xml +++ b/doc/classes/Input.xml @@ -208,7 +208,7 @@ <argument index="4" name="deadzone" type="float" default="-1.0"> </argument> <description> - Get vector input by specifying four actions, two for the X axis and two for the Y axis, negative and positive. + Gets an input vector by specifying four actions for the positive and negative X and Y axes. This method is useful when getting vector input, such as from a joystick, directional pad, arrows, or WASD. The vector has its length limited to 1 and has a circular deadzone, which is useful for using vector input as movement. By default, the deadzone is automatically calculated from the average of the action deadzones. However, you can override the deadzone to be whatever you want (on the range of 0 to 1). </description> diff --git a/doc/classes/Label.xml b/doc/classes/Label.xml index ca22567ad5..9e96f11ccc 100644 --- a/doc/classes/Label.xml +++ b/doc/classes/Label.xml @@ -4,7 +4,7 @@ Displays plain text in a line or wrapped inside a rectangle. For formatted text, use [RichTextLabel]. </brief_description> <description> - Label displays plain text on the screen. It gives you control over the horizontal and vertical alignment, and can wrap the text inside the node's bounding rectangle. It doesn't support bold, italics or other formatting. For that, use [RichTextLabel] instead. + Label displays plain text on the screen. It gives you control over the horizontal and vertical alignment and can wrap the text inside the node's bounding rectangle. It doesn't support bold, italics, or other formatting. For that, use [RichTextLabel] instead. [b]Note:[/b] Contrarily to most other [Control]s, Label's [member Control.mouse_filter] defaults to [constant Control.MOUSE_FILTER_IGNORE] (i.e. it doesn't react to mouse input events). This implies that a label won't display any configured [member Control.hint_tooltip], unless you change its mouse filter. </description> <tutorials> diff --git a/doc/classes/MultiMesh.xml b/doc/classes/MultiMesh.xml index 7151e58c5f..45326f12e9 100644 --- a/doc/classes/MultiMesh.xml +++ b/doc/classes/MultiMesh.xml @@ -6,7 +6,7 @@ <description> MultiMesh provides low-level mesh instancing. Drawing thousands of [MeshInstance3D] nodes can be slow, since each object is submitted to the GPU then drawn individually. MultiMesh is much faster as it can draw thousands of instances with a single draw call, resulting in less API overhead. - As a drawback, if the instances are too far away of each other, performance may be reduced as every single instance will always render (they are spatially indexed as one, for the whole object). + As a drawback, if the instances are too far away from each other, performance may be reduced as every single instance will always render (they are spatially indexed as one, for the whole object). Since instances may have any behavior, the AABB used for visibility must be provided by the user. </description> <tutorials> diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index a9396306f4..679a51935d 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -10,6 +10,17 @@ <link title="OS Test Demo">https://godotengine.org/asset-library/asset/677</link> </tutorials> <methods> + <method name="alert"> + <return type="void"> + </return> + <argument index="0" name="text" type="String"> + </argument> + <argument index="1" name="title" type="String" default=""Alert!""> + </argument> + <description> + Displays a modal dialog box using the host OS' facilities. Execution is blocked until the dialog is closed. + </description> + </method> <method name="can_use_threads" qualifiers="const"> <return type="bool"> </return> @@ -54,7 +65,7 @@ <argument index="0" name="msec" type="int"> </argument> <description> - Delay execution of the current thread by [code]msec[/code] milliseconds. [code]usec[/code] must be greater than or equal to [code]0[/code]. Otherwise, [method delay_msec] will do nothing and will print an error message. + Delay execution of the current thread by [code]msec[/code] milliseconds. [code]msec[/code] must be greater than or equal to [code]0[/code]. Otherwise, [method delay_msec] will do nothing and will print an error message. </description> </method> <method name="delay_usec" qualifiers="const"> @@ -228,7 +239,7 @@ <return type="PackedStringArray"> </return> <description> - With this function you can get the list of dangerous permissions that have been granted to the Android application. + With this function, you can get the list of dangerous permissions that have been granted to the Android application. [b]Note:[/b] This method is implemented on Android. </description> </method> @@ -250,7 +261,7 @@ [code]language[/code] - 2 or 3-letter [url=https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes]language code[/url], in lower case. [code]Script[/code] - optional, 4-letter [url=https://en.wikipedia.org/wiki/ISO_15924]script code[/url], in title case. [code]COUNTRY[/code] - optional, 2 or 3-letter [url=https://en.wikipedia.org/wiki/ISO_3166-1]country code[/url], in upper case. - [code]VARIANT[/code] - optional, language variant, region and sort order. Variant can have any number of underscored key words. + [code]VARIANT[/code] - optional, language variant, region and sort order. Variant can have any number of underscored keywords. [code]extra[/code] - optional, semicolon separated list of additional key words. Currency, calendar, sort order and numbering system information. </description> </method> @@ -321,7 +332,7 @@ </return> <description> Returns a string that is unique to the device. - [b]Note:[/b] This string may change without notice if the user reinstalls/upgrades their operating system or changes their hardware. This means it should generally not be used to encrypt persistent data as the data saved prior to an unexpected ID change would become inaccessible. The returned string may also be falsified using external programs, so do not rely on the string returned by [method get_unique_id] for security purposes. + [b]Note:[/b] This string may change without notice if the user reinstalls/upgrades their operating system or changes their hardware. This means it should generally not be used to encrypt persistent data as the data saved before an unexpected ID change would become inaccessible. The returned string may also be falsified using external programs, so do not rely on the string returned by [method get_unique_id] for security purposes. [b]Note:[/b] Returns an empty string on HTML5 and UWP, as this method isn't implemented on those platforms yet. </description> </method> @@ -353,7 +364,7 @@ <argument index="0" name="tag_name" type="String"> </argument> <description> - Returns [code]true[/code] if the feature for the given feature tag is supported in the currently running instance, depending on platform, build etc. Can be used to check whether you're currently running a debug build, on a certain platform or arch, etc. Refer to the [url=https://docs.godotengine.org/en/latest/getting_started/workflow/export/feature_tags.html]Feature Tags[/url] documentation for more details. + Returns [code]true[/code] if the feature for the given feature tag is supported in the currently running instance, depending on the platform, build, etc. Can be used to check whether you're currently running a debug build, on a certain platform or arch, etc. Refer to the [url=https://docs.godotengine.org/en/latest/getting_started/workflow/export/feature_tags.html]Feature Tags[/url] documentation for more details. [b]Note:[/b] Tag names are case-sensitive. </description> </method> @@ -455,7 +466,7 @@ <return type="bool"> </return> <description> - With this function you can request dangerous permissions since normal permissions are automatically granted at install time in Android application. + With this function, you can request dangerous permissions since normal permissions are automatically granted at install time in Android applications. [b]Note:[/b] This method is implemented on Android. </description> </method> diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index cdf76a3a59..fe7529bceb 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -400,7 +400,7 @@ <method name="get_meta" qualifiers="const"> <return type="Variant"> </return> - <argument index="0" name="name" type="String"> + <argument index="0" name="name" type="StringName"> </argument> <description> Returns the object's metadata entry for the given [code]name[/code]. @@ -454,7 +454,7 @@ <method name="has_meta" qualifiers="const"> <return type="bool"> </return> - <argument index="0" name="name" type="String"> + <argument index="0" name="name" type="StringName"> </argument> <description> Returns [code]true[/code] if a metadata entry is found with the given [code]name[/code]. @@ -543,7 +543,7 @@ <method name="remove_meta"> <return type="void"> </return> - <argument index="0" name="name" type="String"> + <argument index="0" name="name" type="StringName"> </argument> <description> Removes a given entry from the object's metadata. See also [method set_meta]. @@ -619,7 +619,7 @@ <method name="set_meta"> <return type="void"> </return> - <argument index="0" name="name" type="String"> + <argument index="0" name="name" type="StringName"> </argument> <argument index="1" name="value" type="Variant"> </argument> diff --git a/doc/classes/ParticlesMaterial.xml b/doc/classes/ParticlesMaterial.xml index 52430b3f45..640a26b8cb 100644 --- a/doc/classes/ParticlesMaterial.xml +++ b/doc/classes/ParticlesMaterial.xml @@ -174,6 +174,18 @@ <member name="emission_point_texture" type="Texture2D" setter="set_emission_point_texture" getter="get_emission_point_texture"> Particles will be emitted at positions determined by sampling this texture at a random position. Used with [constant EMISSION_SHAPE_POINTS] and [constant EMISSION_SHAPE_DIRECTED_POINTS]. Can be created automatically from mesh or node by selecting "Create Emission Points from Mesh/Node" under the "Particles" tool in the toolbar. </member> + <member name="emission_ring_axis" type="Vector3" setter="set_emission_ring_axis" getter="get_emission_ring_axis"> + The axis of the ring when using the emitter [constant EMISSION_SHAPE_RING]. + </member> + <member name="emission_ring_height" type="float" setter="set_emission_ring_height" getter="get_emission_ring_height"> + The height of the ring when using the emitter [constant EMISSION_SHAPE_RING]. + </member> + <member name="emission_ring_inner_radius" type="float" setter="set_emission_ring_inner_radius" getter="get_emission_ring_inner_radius"> + The inner radius of the ring when using the emitter [constant EMISSION_SHAPE_RING]. + </member> + <member name="emission_ring_radius" type="float" setter="set_emission_ring_radius" getter="get_emission_ring_radius"> + The radius of the ring when using the emitter [constant EMISSION_SHAPE_RING]. + </member> <member name="emission_shape" type="int" setter="set_emission_shape" getter="get_emission_shape" enum="ParticlesMaterial.EmissionShape" default="0"> Particles will be emitted inside this region. Use [enum EmissionShape] constants for values. </member> @@ -338,7 +350,10 @@ <constant name="EMISSION_SHAPE_DIRECTED_POINTS" value="4" enum="EmissionShape"> Particles will be emitted at a position determined by sampling a random point on the [member emission_point_texture]. Particle velocity and rotation will be set based on [member emission_normal_texture]. Particle color will be modulated by [member emission_color_texture]. </constant> - <constant name="EMISSION_SHAPE_MAX" value="5" enum="EmissionShape"> + <constant name="EMISSION_SHAPE_RING" value="5" enum="EmissionShape"> + Particles will be emitted in a ring or cylinder. + </constant> + <constant name="EMISSION_SHAPE_MAX" value="6" enum="EmissionShape"> Represents the size of the [enum EmissionShape] enum. </constant> <constant name="SUB_EMITTER_DISABLED" value="0" enum="SubEmitterMode"> diff --git a/doc/classes/ResourceImporter.xml b/doc/classes/ResourceImporter.xml index 59900b1b73..03d47ee518 100644 --- a/doc/classes/ResourceImporter.xml +++ b/doc/classes/ResourceImporter.xml @@ -1,13 +1,22 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="ResourceImporter" inherits="RefCounted" version="4.0"> <brief_description> + Base class for the implementation of core resource importers. </brief_description> <description> + This is the base class for the resource importers implemented in core. To implement your own resource importers using editor plugins, see [EditorImportPlugin]. </description> <tutorials> + <link title="Import plugins">https://docs.godotengine.org/en/latest/tutorials/plugins/editor/import_plugins.html</link> </tutorials> <methods> </methods> <constants> + <constant name="IMPORT_ORDER_DEFAULT" value="0" enum="ImportOrder"> + The default import order. + </constant> + <constant name="IMPORT_ORDER_SCENE" value="100" enum="ImportOrder"> + The import order for scenes, which ensures scenes are imported [i]after[/i] all other core resources such as textures. Custom importers should generally have an import order lower than [code]100[/code] to avoid issues when importing scenes that rely on custom resources. + </constant> </constants> </class> diff --git a/doc/classes/RootMotionView.xml b/doc/classes/RootMotionView.xml index be8d8d0078..e31ea9265e 100644 --- a/doc/classes/RootMotionView.xml +++ b/doc/classes/RootMotionView.xml @@ -1,23 +1,32 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="RootMotionView" inherits="VisualInstance3D" version="4.0"> <brief_description> + Editor-only helper for setting up root motion in [AnimationTree]. </brief_description> <description> + [i]Root motion[/i] refers to an animation technique where a mesh's skeleton is used to give impulse to a character. When working with 3D animations, a popular technique is for animators to use the root skeleton bone to give motion to the rest of the skeleton. This allows animating characters in a way where steps actually match the floor below. It also allows precise interaction with objects during cinematics. See also [AnimationTree]. + [b]Note:[/b] [RootMotionView] is only visible in the editor. It will be hidden automatically in the running project, and will also be converted to a plain [Node] in the running project. This means a script attached to a [RootMotionView] node [i]must[/i] have [code]extends Node[/code] instead of [code]extends RootMotionView[/code]. Additionally, it must not be a [code]@tool[/code] script. </description> <tutorials> + <link title="Using AnimationTree - Root motion">https://docs.godotengine.org/en/latest/tutorials/animation/animation_tree.html#root-motion</link> </tutorials> <methods> </methods> <members> <member name="animation_path" type="NodePath" setter="set_animation_path" getter="get_animation_path"> + Path to an [AnimationTree] node to use as a basis for root motion. </member> <member name="cell_size" type="float" setter="set_cell_size" getter="get_cell_size"> + The grid's cell size in 3D units. </member> <member name="color" type="Color" setter="set_color" getter="get_color"> + The grid's color. </member> <member name="radius" type="float" setter="set_radius" getter="get_radius"> + The grid's radius in 3D units. The grid's opacity will fade gradually as the distance from the origin increases until this [member radius] is reached. </member> <member name="zero_y" type="bool" setter="set_zero_y" getter="get_zero_y"> + If [code]true[/code], the grid's points will all be on the same Y coordinate ([i]local[/i] Y = 0). If [code]false[/code], the points' original Y coordinate is preserved. </member> </members> <constants> diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml index 5a4068ec86..e6f696d27c 100644 --- a/doc/classes/TileMap.xml +++ b/doc/classes/TileMap.xml @@ -36,6 +36,8 @@ </return> <argument index="0" name="coords" type="Vector2i"> </argument> + <argument index="1" name="use_proxies" type="bool"> + </argument> <description> </description> </method> @@ -44,6 +46,8 @@ </return> <argument index="0" name="coords" type="Vector2i"> </argument> + <argument index="1" name="use_proxies" type="bool"> + </argument> <description> </description> </method> @@ -52,6 +56,8 @@ </return> <argument index="0" name="coords" type="Vector2i"> </argument> + <argument index="1" name="use_proxies" type="bool"> + </argument> <description> </description> </method> diff --git a/doc/classes/TileSet.xml b/doc/classes/TileSet.xml index 436e15387d..8185b09a30 100644 --- a/doc/classes/TileSet.xml +++ b/doc/classes/TileSet.xml @@ -27,6 +27,40 @@ <description> </description> </method> + <method name="cleanup_invalid_tile_proxies"> + <return type="void"> + </return> + <description> + </description> + </method> + <method name="clear_tile_proxies"> + <return type="void"> + </return> + <description> + </description> + </method> + <method name="get_alternative_level_tile_proxy"> + <return type="Array"> + </return> + <argument index="0" name="source_from" type="int"> + </argument> + <argument index="1" name="coords_from" type="Vector2i"> + </argument> + <argument index="2" name="alternative_from" type="int"> + </argument> + <description> + </description> + </method> + <method name="get_coords_level_tile_proxy"> + <return type="Array"> + </return> + <argument index="0" name="source_from" type="int"> + </argument> + <argument index="1" name="coords_from" type="Vector2i"> + </argument> + <description> + </description> + </method> <method name="get_navigation_layer_layers" qualifiers="const"> <return type="int"> </return> @@ -103,6 +137,14 @@ <description> </description> </method> + <method name="get_source_level_tile_proxy"> + <return type="int"> + </return> + <argument index="0" name="source_from" type="int"> + </argument> + <description> + </description> + </method> <method name="get_terrain_color" qualifiers="const"> <return type="Color"> </return> @@ -139,6 +181,28 @@ <description> </description> </method> + <method name="has_alternative_level_tile_proxy"> + <return type="bool"> + </return> + <argument index="0" name="source_from" type="int"> + </argument> + <argument index="1" name="coords_from" type="Vector2i"> + </argument> + <argument index="2" name="alternative_from" type="int"> + </argument> + <description> + </description> + </method> + <method name="has_coords_level_tile_proxy"> + <return type="bool"> + </return> + <argument index="0" name="source_from" type="int"> + </argument> + <argument index="1" name="coords_from" type="Vector2i"> + </argument> + <description> + </description> + </method> <method name="has_source" qualifiers="const"> <return type="bool"> </return> @@ -147,6 +211,48 @@ <description> </description> </method> + <method name="has_source_level_tile_proxy"> + <return type="bool"> + </return> + <argument index="0" name="source_from" type="int"> + </argument> + <description> + </description> + </method> + <method name="map_tile_proxy" qualifiers="const"> + <return type="Array"> + </return> + <argument index="0" name="source_from" type="int"> + </argument> + <argument index="1" name="coords_from" type="Vector2i"> + </argument> + <argument index="2" name="alternative_from" type="int"> + </argument> + <description> + </description> + </method> + <method name="remove_alternative_level_tile_proxy"> + <return type="void"> + </return> + <argument index="0" name="source_from" type="int"> + </argument> + <argument index="1" name="coords_from" type="Vector2i"> + </argument> + <argument index="2" name="alternative_from" type="int"> + </argument> + <description> + </description> + </method> + <method name="remove_coords_level_tile_proxy"> + <return type="void"> + </return> + <argument index="0" name="source_from" type="int"> + </argument> + <argument index="1" name="coords_from" type="Vector2i"> + </argument> + <description> + </description> + </method> <method name="remove_source"> <return type="void"> </return> @@ -155,6 +261,46 @@ <description> </description> </method> + <method name="remove_source_level_tile_proxy"> + <return type="void"> + </return> + <argument index="0" name="source_from" type="int"> + </argument> + <description> + </description> + </method> + <method name="set_alternative_level_tile_proxy"> + <return type="void"> + </return> + <argument index="0" name="source_from" type="int"> + </argument> + <argument index="1" name="coords_from" type="Vector2i"> + </argument> + <argument index="2" name="alternative_from" type="int"> + </argument> + <argument index="3" name="source_to" type="int"> + </argument> + <argument index="4" name="coords_to" type="Vector2i"> + </argument> + <argument index="5" name="alternative_to" type="int"> + </argument> + <description> + </description> + </method> + <method name="set_coords_level_tile_proxy"> + <return type="void"> + </return> + <argument index="0" name="p_source_from" type="int"> + </argument> + <argument index="1" name="coords_from" type="Vector2i"> + </argument> + <argument index="2" name="source_to" type="int"> + </argument> + <argument index="3" name="coords_to" type="Vector2i"> + </argument> + <description> + </description> + </method> <method name="set_navigation_layer_layers"> <return type="void"> </return> @@ -225,6 +371,16 @@ <description> </description> </method> + <method name="set_source_level_tile_proxy"> + <return type="void"> + </return> + <argument index="0" name="source_from" type="int"> + </argument> + <argument index="1" name="source_to" type="int"> + </argument> + <description> + </description> + </method> <method name="set_terrain_color"> <return type="void"> </return> diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index a5c61bbea5..f6a3e93b55 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -139,10 +139,6 @@ void OS_Unix::finalize_core() { NetSocketPosix::cleanup(); } -void OS_Unix::alert(const String &p_alert, const String &p_title) { - fprintf(stderr, "ERROR: %s\n", p_alert.utf8().get_data()); -} - String OS_Unix::get_stdin_string(bool p_block) { if (p_block) { char buff[1024]; diff --git a/drivers/unix/os_unix.h b/drivers/unix/os_unix.h index 6c79d984e9..bf82019d38 100644 --- a/drivers/unix/os_unix.h +++ b/drivers/unix/os_unix.h @@ -52,7 +52,6 @@ protected: public: OS_Unix(); - virtual void alert(const String &p_alert, const String &p_title = "ALERT!"); virtual String get_stdin_string(bool p_block) override; //virtual void set_mouse_show(bool p_show); diff --git a/drivers/vulkan/SCsub b/drivers/vulkan/SCsub index 14b9d63204..6a2a57d18c 100644 --- a/drivers/vulkan/SCsub +++ b/drivers/vulkan/SCsub @@ -105,7 +105,7 @@ elif env["builtin_vulkan"]: else: # Always build VMA. thirdparty_dir = "#thirdparty/vulkan" - env.Prepend(CPPPATH=[thirdparty_dir]) + env.Prepend(CPPPATH=[thirdparty_dir, thirdparty_dir + "/include"]) # Build Vulkan loader library env_thirdparty = env.Clone() diff --git a/editor/import/resource_importer_scene.h b/editor/import/resource_importer_scene.h index c6e5836a23..781beff689 100644 --- a/editor/import/resource_importer_scene.h +++ b/editor/import/resource_importer_scene.h @@ -155,7 +155,8 @@ public: virtual void get_import_options(List<ImportOption> *r_options, int p_preset = 0) const override; virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const override; - virtual int get_import_order() const override { return 100; } //after everything + // Import scenes *after* everything else (such as textures). + virtual int get_import_order() const override { return ResourceImporter::IMPORT_ORDER_SCENE; } Node *_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, List<Ref<Shape3D>>> &collision_map); Node *_post_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, List<Ref<Shape3D>>> &collision_map, Set<Ref<EditorSceneImporterMesh>> &r_scanned_meshes, const Dictionary &p_node_data, const Dictionary &p_material_data, const Dictionary &p_animation_data, float p_animation_fps); diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp index bbc8107cf6..556f35d4b0 100644 --- a/editor/plugins/asset_library_editor_plugin.cpp +++ b/editor/plugins/asset_library_editor_plugin.cpp @@ -603,8 +603,7 @@ void EditorAssetLibrary::_notification(int p_what) { void EditorAssetLibrary::_update_repository_options() { Dictionary default_urls; - default_urls["godotengine.org"] = "https://godotengine.org/asset-library/api"; - default_urls["localhost"] = "http://127.0.0.1/asset-library/api"; + default_urls["godotengine.org (Official)"] = "https://godotengine.org/asset-library/api"; Dictionary available_urls = _EDITOR_DEF("asset_library/available_urls", default_urls, true); repository->clear(); Array keys = available_urls.keys(); diff --git a/editor/plugins/collision_shape_2d_editor_plugin.cpp b/editor/plugins/collision_shape_2d_editor_plugin.cpp index 45adbe29de..2c2adc2672 100644 --- a/editor/plugins/collision_shape_2d_editor_plugin.cpp +++ b/editor/plugins/collision_shape_2d_editor_plugin.cpp @@ -31,6 +31,7 @@ #include "collision_shape_2d_editor_plugin.h" #include "canvas_item_editor_plugin.h" +#include "core/os/keyboard.h" #include "scene/resources/capsule_shape_2d.h" #include "scene/resources/circle_shape_2d.h" #include "scene/resources/concave_polygon_shape_2d.h" @@ -97,7 +98,7 @@ Variant CollisionShape2DEditor::get_handle_value(int idx) const { case RECTANGLE_SHAPE: { Ref<RectangleShape2D> rect = node->get_shape(); - if (idx < 3) { + if (idx < 8) { return rect->get_size().abs(); } @@ -176,16 +177,26 @@ void CollisionShape2DEditor::set_handle(int idx, Point2 &p_point) { } break; case RECTANGLE_SHAPE: { - if (idx < 3) { + if (idx < 8) { Ref<RectangleShape2D> rect = node->get_shape(); + Vector2 size = (Point2)original; - Vector2 size = rect->get_size(); - if (idx == 2) { - size = p_point * 2; + if (RECT_HANDLES[idx].x != 0) { + size.x = p_point.x * RECT_HANDLES[idx].x * 2; + } + if (RECT_HANDLES[idx].y != 0) { + size.y = p_point.y * RECT_HANDLES[idx].y * 2; + } + + if (Input::get_singleton()->is_key_pressed(KEY_ALT)) { + rect->set_size(size.abs()); + node->set_global_position(original_transform.get_origin()); } else { - size[idx] = p_point[idx] * 2; + rect->set_size(((Point2)original + (size - (Point2)original) * 0.5).abs()); + Point2 pos = original_transform.affine_inverse().xform(original_transform.get_origin()); + pos += (size - (Point2)original) * 0.5 * RECT_HANDLES[idx] * 0.5; + node->set_global_position(original_transform.xform(pos)); } - rect->set_size(size.abs()); canvas_item_editor->update_viewport(); } @@ -280,8 +291,10 @@ void CollisionShape2DEditor::commit_handle(int idx, Variant &p_org) { Ref<RectangleShape2D> rect = node->get_shape(); undo_redo->add_do_method(rect.ptr(), "set_size", rect->get_size()); + undo_redo->add_do_method(node, "set_global_transform", node->get_global_transform()); undo_redo->add_do_method(canvas_item_editor, "update_viewport"); undo_redo->add_undo_method(rect.ptr(), "set_size", p_org); + undo_redo->add_undo_method(node, "set_global_transform", original_transform); undo_redo->add_undo_method(canvas_item_editor, "update_viewport"); } break; @@ -342,6 +355,8 @@ bool CollisionShape2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_e } original = get_handle_value(edit_handle); + original_transform = node->get_global_transform(); + last_point = original; pressed = true; return true; @@ -369,13 +384,26 @@ bool CollisionShape2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_e } Vector2 cpoint = canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(mm->get_position())); - cpoint = node->get_global_transform().affine_inverse().xform(cpoint); + cpoint = original_transform.affine_inverse().xform(cpoint); + last_point = cpoint; set_handle(edit_handle, cpoint); return true; } + Ref<InputEventKey> k = p_event; + + if (k.is_valid()) { + if (edit_handle == -1 || !pressed || k->is_echo()) { + return false; + } + + if (shape_type == RECTANGLE_SHAPE && k->get_keycode() == KEY_ALT) { + set_handle(edit_handle, last_point); // Update handle when Alt key is toggled. + } + } + return false; } @@ -492,15 +520,12 @@ void CollisionShape2DEditor::forward_canvas_draw_over_viewport(Control *p_overla case RECTANGLE_SHAPE: { Ref<RectangleShape2D> shape = node->get_shape(); - handles.resize(3); + handles.resize(8); Vector2 ext = shape->get_size() / 2; - handles.write[0] = Point2(ext.x, 0); - handles.write[1] = Point2(0, ext.y); - handles.write[2] = Point2(ext.x, ext.y); - - p_overlay->draw_texture(h, gt.xform(handles[0]) - size); - p_overlay->draw_texture(h, gt.xform(handles[1]) - size); - p_overlay->draw_texture(h, gt.xform(handles[2]) - size); + for (int i = 0; i < handles.size(); i++) { + handles.write[i] = RECT_HANDLES[i] * ext; + p_overlay->draw_texture(h, gt.xform(handles[i]) - size); + } } break; diff --git a/editor/plugins/collision_shape_2d_editor_plugin.h b/editor/plugins/collision_shape_2d_editor_plugin.h index 054db1a61b..7db6bd22aa 100644 --- a/editor/plugins/collision_shape_2d_editor_plugin.h +++ b/editor/plugins/collision_shape_2d_editor_plugin.h @@ -52,6 +52,17 @@ class CollisionShape2DEditor : public Control { SEGMENT_SHAPE }; + const Point2 RECT_HANDLES[8] = { + Point2(1, 0), + Point2(1, 1), + Point2(0, 1), + Point2(-1, 1), + Point2(-1, 0), + Point2(-1, -1), + Point2(0, -1), + Point2(1, -1), + }; + EditorNode *editor; UndoRedo *undo_redo; CanvasItemEditor *canvas_item_editor; @@ -63,6 +74,8 @@ class CollisionShape2DEditor : public Control { int edit_handle; bool pressed; Variant original; + Transform2D original_transform; + Point2 last_point; Variant get_handle_value(int idx) const; void set_handle(int idx, Point2 &p_point); diff --git a/editor/plugins/texture_editor_plugin.cpp b/editor/plugins/texture_editor_plugin.cpp index 4029d6785c..44db06bcfd 100644 --- a/editor/plugins/texture_editor_plugin.cpp +++ b/editor/plugins/texture_editor_plugin.cpp @@ -36,9 +36,30 @@ TextureRect *TexturePreview::get_texture_display() { return texture_display; } +void TexturePreview::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + if (!is_inside_tree()) { + // TODO: This is a workaround because `NOTIFICATION_THEME_CHANGED` + // is getting called for some reason when the `TexturePreview` is + // getting destroyed, which causes `get_theme_font()` to return `nullptr`. + // See https://github.com/godotengine/godot/issues/50743. + break; + } + + if (metadata_label) { + Ref<Font> metadata_label_font = get_theme_font(SNAME("expression"), SNAME("EditorFonts")); + metadata_label->add_theme_font_override("font", metadata_label_font); + } + + checkerboard->set_texture(get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons"))); + } break; + } +} + TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) { - TextureRect *checkerboard = memnew(TextureRect); - checkerboard->set_texture(get_theme_icon("Checkerboard", "EditorIcons")); + checkerboard = memnew(TextureRect); checkerboard->set_stretch_mode(TextureRect::STRETCH_TILE); checkerboard->set_texture_repeat(CanvasItem::TEXTURE_REPEAT_ENABLED); checkerboard->set_custom_minimum_size(Size2(0.0, 256.0) * EDSCALE); @@ -52,7 +73,7 @@ TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) { add_child(texture_display); if (p_show_metadata) { - Label *metadata_label = memnew(Label); + metadata_label = memnew(Label); String format; if (Object::cast_to<ImageTexture>(*p_texture)) { @@ -65,16 +86,14 @@ TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) { metadata_label->set_text(itos(p_texture->get_width()) + "x" + itos(p_texture->get_height()) + " " + format); - metadata_label->add_theme_font_size_override("font_size", 16 * EDSCALE); - metadata_label->add_theme_color_override("font_outline_color", Color::named("black")); - metadata_label->add_theme_constant_override("outline_size", 2 * EDSCALE); - Ref<Font> metadata_label_font = get_theme_font("expression", "EditorFonts"); - metadata_label->add_theme_font_override("font", metadata_label_font); - // It's okay that these colors are static since the grid color is static too. metadata_label->add_theme_color_override("font_color", Color::named("white")); metadata_label->add_theme_color_override("font_color_shadow", Color::named("black")); + metadata_label->add_theme_font_size_override("font_size", 16 * EDSCALE); + metadata_label->add_theme_color_override("font_outline_color", Color::named("black")); + metadata_label->add_theme_constant_override("outline_size", 2 * EDSCALE); + metadata_label->add_theme_constant_override("shadow_as_outline", 1); metadata_label->set_h_size_flags(Control::SIZE_SHRINK_END); metadata_label->set_v_size_flags(Control::SIZE_SHRINK_END); diff --git a/editor/plugins/texture_editor_plugin.h b/editor/plugins/texture_editor_plugin.h index 165090be93..36a5513ea6 100644 --- a/editor/plugins/texture_editor_plugin.h +++ b/editor/plugins/texture_editor_plugin.h @@ -39,7 +39,13 @@ class TexturePreview : public MarginContainer { GDCLASS(TexturePreview, MarginContainer); private: - TextureRect *texture_display; + TextureRect *texture_display = nullptr; + + TextureRect *checkerboard = nullptr; + Label *metadata_label = nullptr; + +protected: + void _notification(int p_what); public: TextureRect *get_texture_display(); diff --git a/editor/plugins/tiles/atlas_merging_dialog.cpp b/editor/plugins/tiles/atlas_merging_dialog.cpp new file mode 100644 index 0000000000..bbafc7802b --- /dev/null +++ b/editor/plugins/tiles/atlas_merging_dialog.cpp @@ -0,0 +1,320 @@ +/*************************************************************************/ +/* atlas_merging_dialog.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 "atlas_merging_dialog.h" + +#include "editor/editor_scale.h" + +#include "scene/gui/control.h" +#include "scene/gui/split_container.h" + +void AtlasMergingDialog::_property_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing) { + _set(p_property, p_value); +} + +void AtlasMergingDialog::_generate_merged(Vector<Ref<TileSetAtlasSource>> p_atlas_sources, int p_max_columns) { + merged.instantiate(); + merged_mapping.clear(); + + if (p_atlas_sources.size() >= 2) { + Ref<Image> output_image; + output_image.instantiate(); + output_image->create(1, 1, false, Image::FORMAT_RGBA8); + + // Compute the new texture region size. + Vector2i new_texture_region_size; + for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) { + Ref<TileSetAtlasSource> atlas_source = p_atlas_sources[source_index]; + new_texture_region_size = new_texture_region_size.max(atlas_source->get_texture_region_size()); + } + + // Generate the merged TileSetAtlasSource. + Vector2i atlas_offset; + int line_height = 0; + for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) { + Ref<TileSetAtlasSource> atlas_source = p_atlas_sources[source_index]; + merged_mapping.push_back(Map<Vector2i, Vector2i>()); + + // Layout the tiles. + Vector2i atlas_size; + + for (int tile_index = 0; tile_index < atlas_source->get_tiles_count(); tile_index++) { + Vector2i tile_id = atlas_source->get_tile_id(tile_index); + atlas_size = atlas_size.max(tile_id + atlas_source->get_tile_size_in_atlas(tile_id)); + + Rect2i new_tile_rect_in_altas = Rect2i(atlas_offset + tile_id, atlas_source->get_tile_size_in_atlas(tile_id)); + + // Create tiles and alternatives, then copy their properties. + for (int alternative_index = 0; alternative_index < atlas_source->get_alternative_tiles_count(tile_id); alternative_index++) { + int alternative_id = atlas_source->get_alternative_tile_id(tile_id, alternative_index); + if (alternative_id == 0) { + merged->create_tile(new_tile_rect_in_altas.position, new_tile_rect_in_altas.size); + } else { + merged->create_alternative_tile(new_tile_rect_in_altas.position, alternative_index); + } + + // Copy the properties. + TileData *original_tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(tile_id, alternative_id)); + List<PropertyInfo> properties; + original_tile_data->get_property_list(&properties); + for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { + const StringName &property_name = E->get().name; + merged->set(property_name, original_tile_data->get(property_name)); + } + + // Add to the mapping. + merged_mapping[source_index][tile_id] = new_tile_rect_in_altas.position; + } + + // Copy the texture. + Rect2i src_rect = atlas_source->get_tile_texture_region(tile_id); + Rect2 dst_rect_wide = Rect2i(new_tile_rect_in_altas.position * new_texture_region_size, new_tile_rect_in_altas.size * new_texture_region_size); + if (dst_rect_wide.get_end().x > output_image->get_width() || dst_rect_wide.get_end().y > output_image->get_height()) { + output_image->crop(MAX(dst_rect_wide.get_end().x, output_image->get_width()), MAX(dst_rect_wide.get_end().y, output_image->get_height())); + } + output_image->blit_rect(atlas_source->get_texture()->get_image(), src_rect, (dst_rect_wide.get_position() + dst_rect_wide.get_end()) / 2 - src_rect.size / 2); + } + + // Compute the atlas offset. + line_height = MAX(atlas_size.y, line_height); + atlas_offset.x += atlas_size.x; + if (atlas_offset.x >= p_max_columns) { + atlas_offset.x = 0; + atlas_offset.y += line_height; + line_height = 0; + } + } + + Ref<ImageTexture> output_image_texture; + output_image_texture.instantiate(); + output_image_texture->create_from_image(output_image); + + merged->set_texture(output_image_texture); + merged->set_texture_region_size(new_texture_region_size); + } +} + +void AtlasMergingDialog::_update_texture() { + Vector<int> selected = atlas_merging_atlases_list->get_selected_items(); + if (selected.size() >= 2) { + Vector<Ref<TileSetAtlasSource>> to_merge; + for (int i = 0; i < selected.size(); i++) { + int source_id = atlas_merging_atlases_list->get_item_metadata(selected[i]); + to_merge.push_back(tile_set->get_source(source_id)); + } + _generate_merged(to_merge, next_line_after_column); + preview->set_texture(merged->get_texture()); + preview->show(); + select_2_atlases_label->hide(); + get_ok_button()->set_disabled(false); + merge_button->set_disabled(false); + } else { + _generate_merged(Vector<Ref<TileSetAtlasSource>>(), next_line_after_column); + preview->set_texture(Ref<Texture2D>()); + preview->hide(); + select_2_atlases_label->show(); + get_ok_button()->set_disabled(true); + merge_button->set_disabled(true); + } +} + +void AtlasMergingDialog::_merge_confirmed(String p_path) { + ERR_FAIL_COND(!merged.is_valid()); + + Ref<ImageTexture> output_image_texture = merged->get_texture(); + output_image_texture->get_image()->save_png(p_path); + + Ref<Texture2D> new_texture_resource = ResourceLoader::load(p_path, "Texture2D"); + merged->set_texture(new_texture_resource); + + undo_redo->create_action("Merge TileSetAtlasSource"); + int next_id = tile_set->get_next_source_id(); + undo_redo->add_do_method(*tile_set, "add_source", merged, next_id); + undo_redo->add_undo_method(*tile_set, "remove_source", next_id); + + if (delete_original_atlases) { + // Delete originals if needed. + Vector<int> selected = atlas_merging_atlases_list->get_selected_items(); + for (int i = 0; i < selected.size(); i++) { + int source_id = atlas_merging_atlases_list->get_item_metadata(selected[i]); + Ref<TileSetAtlasSource> tas = tile_set->get_source(source_id); + undo_redo->add_do_method(*tile_set, "remove_source", source_id); + undo_redo->add_undo_method(*tile_set, "add_source", tas, source_id); + + // Add the tile proxies. + for (int tile_index = 0; tile_index < tas->get_tiles_count(); tile_index++) { + Vector2i tile_id = tas->get_tile_id(tile_index); + undo_redo->add_do_method(*tile_set, "set_coords_level_tile_proxy", source_id, tile_id, next_id, merged_mapping[i][tile_id]); + if (tile_set->has_coords_level_tile_proxy(source_id, tile_id)) { + Array a = tile_set->get_coords_level_tile_proxy(source_id, tile_id); + undo_redo->add_undo_method(*tile_set, "set_coords_level_tile_proxy", a[0], a[1]); + } else { + undo_redo->add_undo_method(*tile_set, "remove_coords_level_tile_proxy", source_id, tile_id); + } + } + } + } + undo_redo->commit_action(); + commited_actions_count++; + + hide(); +} + +void AtlasMergingDialog::ok_pressed() { + delete_original_atlases = false; + editor_file_dialog->popup_file_dialog(); +} + +void AtlasMergingDialog::cancel_pressed() { + for (int i = 0; i < commited_actions_count; i++) { + undo_redo->undo(); + } + commited_actions_count = 0; +} + +void AtlasMergingDialog::custom_action(const String &p_action) { + if (p_action == "merge") { + delete_original_atlases = true; + editor_file_dialog->popup_file_dialog(); + } +} + +bool AtlasMergingDialog::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "next_line_after_column" && p_value.get_type() == Variant::INT) { + next_line_after_column = p_value; + _update_texture(); + return true; + } + return false; +} + +bool AtlasMergingDialog::_get(const StringName &p_name, Variant &r_ret) const { + if (p_name == "next_line_after_column") { + r_ret = next_line_after_column; + return true; + } + return false; +} + +void AtlasMergingDialog::update_tile_set(Ref<TileSet> p_tile_set) { + ERR_FAIL_COND(!p_tile_set.is_valid()); + tile_set = p_tile_set; + + atlas_merging_atlases_list->clear(); + for (int i = 0; i < p_tile_set->get_source_count(); i++) { + int source_id = p_tile_set->get_source_id(i); + Ref<TileSetAtlasSource> atlas_source = p_tile_set->get_source(source_id); + if (atlas_source.is_valid()) { + Ref<Texture2D> texture = atlas_source->get_texture(); + if (texture.is_valid()) { + String item_text = vformat("%s (id:%d)", texture->get_path().get_file(), source_id); + atlas_merging_atlases_list->add_item(item_text, texture); + atlas_merging_atlases_list->set_item_metadata(atlas_merging_atlases_list->get_item_count() - 1, source_id); + } + } + } + + get_ok_button()->set_disabled(true); + merge_button->set_disabled(true); + + commited_actions_count = 0; +} + +AtlasMergingDialog::AtlasMergingDialog() { + // Atlas merging window. + set_title(TTR("Atlas Merging")); + set_hide_on_ok(false); + + // Ok buttons + get_ok_button()->set_text(TTR("Merge (Keep original Atlases)")); + get_ok_button()->set_disabled(true); + merge_button = add_button(TTR("Merge"), true, "merge"); + merge_button->set_disabled(true); + + HSplitContainer *atlas_merging_h_split_container = memnew(HSplitContainer); + atlas_merging_h_split_container->set_h_size_flags(Control::SIZE_EXPAND_FILL); + atlas_merging_h_split_container->set_v_size_flags(Control::SIZE_EXPAND_FILL); + add_child(atlas_merging_h_split_container); + + // Atlas sources item list. + atlas_merging_atlases_list = memnew(ItemList); + atlas_merging_atlases_list->set_fixed_icon_size(Size2i(60, 60) * EDSCALE); + atlas_merging_atlases_list->set_h_size_flags(Control::SIZE_EXPAND_FILL); + atlas_merging_atlases_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); + atlas_merging_atlases_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); + atlas_merging_atlases_list->set_custom_minimum_size(Size2(100, 200)); + atlas_merging_atlases_list->set_select_mode(ItemList::SELECT_MULTI); + atlas_merging_atlases_list->connect("multi_selected", callable_mp(this, &AtlasMergingDialog::_update_texture).unbind(2)); + atlas_merging_h_split_container->add_child(atlas_merging_atlases_list); + + VBoxContainer *atlas_merging_right_panel = memnew(VBoxContainer); + atlas_merging_right_panel->set_h_size_flags(Control::SIZE_EXPAND_FILL); + atlas_merging_h_split_container->add_child(atlas_merging_right_panel); + + // Settings. + Label *settings_label = memnew(Label); + settings_label->set_text(TTR("Settings:")); + atlas_merging_right_panel->add_child(settings_label); + + columns_editor_property = memnew(EditorPropertyInteger); + columns_editor_property->set_label(TTR("Next Line After Column")); + columns_editor_property->set_object_and_property(this, "next_line_after_column"); + columns_editor_property->update_property(); + columns_editor_property->connect("property_changed", callable_mp(this, &AtlasMergingDialog::_property_changed)); + atlas_merging_right_panel->add_child(columns_editor_property); + + // Preview. + Label *preview_label = memnew(Label); + preview_label->set_text(TTR("Preview:")); + atlas_merging_right_panel->add_child(preview_label); + + preview = memnew(TextureRect); + preview->set_h_size_flags(Control::SIZE_EXPAND_FILL); + preview->set_v_size_flags(Control::SIZE_EXPAND_FILL); + preview->set_expand(true); + preview->hide(); + preview->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); + atlas_merging_right_panel->add_child(preview); + + select_2_atlases_label = memnew(Label); + select_2_atlases_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + select_2_atlases_label->set_v_size_flags(Control::SIZE_EXPAND_FILL); + select_2_atlases_label->set_align(Label::ALIGN_CENTER); + select_2_atlases_label->set_valign(Label::VALIGN_CENTER); + select_2_atlases_label->set_text(TTR("Please select two atlases or more.")); + atlas_merging_right_panel->add_child(select_2_atlases_label); + + // The file dialog to choose the texture path. + editor_file_dialog = memnew(EditorFileDialog); + editor_file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); + editor_file_dialog->add_filter("*.png"); + editor_file_dialog->connect("file_selected", callable_mp(this, &AtlasMergingDialog::_merge_confirmed)); + add_child(editor_file_dialog); +} diff --git a/editor/plugins/tiles/atlas_merging_dialog.h b/editor/plugins/tiles/atlas_merging_dialog.h new file mode 100644 index 0000000000..7cb54bc17e --- /dev/null +++ b/editor/plugins/tiles/atlas_merging_dialog.h @@ -0,0 +1,86 @@ +/*************************************************************************/ +/* atlas_merging_dialog.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef ATLAS_MERGING_DIALOG_H +#define ATLAS_MERGING_DIALOG_H + +#include "editor/editor_node.h" +#include "editor/editor_properties.h" + +#include "scene/gui/dialogs.h" +#include "scene/gui/item_list.h" +#include "scene/gui/texture_rect.h" +#include "scene/resources/tile_set.h" + +class AtlasMergingDialog : public ConfirmationDialog { + GDCLASS(AtlasMergingDialog, ConfirmationDialog); + +private: + int commited_actions_count = 0; + bool delete_original_atlases = true; + Ref<TileSetAtlasSource> merged; + LocalVector<Map<Vector2i, Vector2i>> merged_mapping; + Ref<TileSet> tile_set; + + UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + + // Settings. + int next_line_after_column = 30; + + // GUI. + ItemList *atlas_merging_atlases_list; + EditorPropertyVector2i *texture_region_size_editor_property; + EditorPropertyInteger *columns_editor_property; + TextureRect *preview; + Label *select_2_atlases_label; + EditorFileDialog *editor_file_dialog; + Button *merge_button; + + void _property_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing); + + void _generate_merged(Vector<Ref<TileSetAtlasSource>> p_atlas_sources, int p_max_columns); + void _update_texture(); + void _merge_confirmed(String p_path); + +protected: + virtual void ok_pressed() override; + virtual void cancel_pressed() override; + virtual void custom_action(const String &) override; + + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + +public: + void update_tile_set(Ref<TileSet> p_tile_set); + + AtlasMergingDialog(); +}; + +#endif // ATLAS_MERGING_DIALOG_H diff --git a/editor/plugins/tiles/tile_atlas_view.h b/editor/plugins/tiles/tile_atlas_view.h index bafc2b3985..b2046f4322 100644 --- a/editor/plugins/tiles/tile_atlas_view.h +++ b/editor/plugins/tiles/tile_atlas_view.h @@ -47,7 +47,7 @@ class TileAtlasView : public Control { private: TileSet *tile_set; TileSetAtlasSource *tile_set_atlas_source; - int source_id = -1; + int source_id = TileSet::INVALID_SOURCE; enum DragType { DRAG_TYPE_NONE, diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp index 6e3c55d801..44822bec3a 100644 --- a/editor/plugins/tiles/tile_map_editor.cpp +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -56,18 +56,11 @@ void TileMapEditorTilesPlugin::_notification(int p_what) { picker_button->set_icon(get_theme_icon(SNAME("ColorPick"), SNAME("EditorIcons"))); erase_button->set_icon(get_theme_icon(SNAME("Eraser"), SNAME("EditorIcons"))); - toggle_grid_button->set_icon(get_theme_icon(SNAME("Grid"), SNAME("EditorIcons"))); - missing_atlas_texture_icon = get_theme_icon(SNAME("TileSet"), SNAME("EditorIcons")); - - toggle_grid_button->set_pressed(EditorSettings::get_singleton()->get("editors/tiles_editor/display_grid")); break; case NOTIFICATION_VISIBILITY_CHANGED: _stop_dragging(); break; - case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: - toggle_grid_button->set_pressed(EditorSettings::get_singleton()->get("editors/tiles_editor/display_grid")); - break; } } @@ -85,10 +78,6 @@ void TileMapEditorTilesPlugin::_on_scattering_spinbox_changed(double p_value) { scattering = p_value; } -void TileMapEditorTilesPlugin::_on_grid_toggled(bool p_pressed) { - EditorSettings::get_singleton()->set("editors/tiles_editor/display_grid", p_pressed); -} - void TileMapEditorTilesPlugin::_update_toolbar() { // Stop draggig if needed. _stop_dragging(); @@ -204,7 +193,7 @@ void TileMapEditorTilesPlugin::_update_tile_set_sources_list() { } // Synchronize - TilesEditor::get_singleton()->set_atlas_sources_lists_current(sources_list->get_current()); + TilesEditor::get_singleton()->set_sources_lists_current(sources_list->get_current()); } void TileMapEditorTilesPlugin::_update_bottom_panel() { @@ -410,7 +399,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p if (!tile_map_selection.is_empty()) { undo_redo->create_action(TTR("Delete tiles")); for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { - undo_redo->add_do_method(tile_map, "set_cell", E->get(), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); + undo_redo->add_do_method(tile_map, "set_cell", E->get(), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); undo_redo->add_undo_method(tile_map, "set_cell", E->get(), tile_map->get_cell_source_id(E->get()), tile_map->get_cell_atlas_coords(E->get()), tile_map->get_cell_alternative_tile(E->get())); } undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection()); @@ -441,7 +430,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p if (!tile_map_selection.is_empty()) { undo_redo->create_action(TTR("Delete tiles")); for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { - undo_redo->add_do_method(tile_map, "set_cell", E->get(), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); + undo_redo->add_do_method(tile_map, "set_cell", E->get(), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); undo_redo->add_undo_method(tile_map, "set_cell", E->get(), tile_map->get_cell_source_id(E->get()), tile_map->get_cell_atlas_coords(E->get()), tile_map->get_cell_alternative_tile(E->get())); } undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection()); @@ -462,12 +451,12 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p case DRAG_TYPE_PAINT: { Map<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, drag_last_mouse_pos, mpos); for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - if (!erase_button->is_pressed() && E->get().source_id == -1) { + if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { continue; } Vector2i coords = E->key(); if (!drag_modified.has(coords)) { - drag_modified.insert(coords, TileMapCell(tile_map->get_cell_source_id(coords), tile_map->get_cell_atlas_coords(coords), tile_map->get_cell_alternative_tile(coords))); + drag_modified.insert(coords, tile_map->get_cell(coords)); } tile_map->set_cell(coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); } @@ -478,12 +467,12 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p if (!drag_modified.has(line[i])) { Map<Vector2i, TileMapCell> to_draw = _draw_bucket_fill(line[i], bucket_continuous_checkbox->is_pressed()); for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - if (!erase_button->is_pressed() && E->get().source_id == -1) { + if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { continue; } Vector2i coords = E->key(); if (!drag_modified.has(coords)) { - drag_modified.insert(coords, TileMapCell(tile_map->get_cell_source_id(coords), tile_map->get_cell_atlas_coords(coords), tile_map->get_cell_alternative_tile(coords))); + drag_modified.insert(coords, tile_map->get_cell(coords)); } tile_map->set_cell(coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); } @@ -516,8 +505,8 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p drag_modified.clear(); for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { Vector2i coords = E->get(); - drag_modified.insert(coords, TileMapCell(tile_map->get_cell_source_id(coords), tile_map->get_cell_atlas_coords(coords), tile_map->get_cell_alternative_tile(coords))); - tile_map->set_cell(coords, -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); + drag_modified.insert(coords, tile_map->get_cell(coords)); + tile_map->set_cell(coords, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); } } else { // Select tiles @@ -536,12 +525,12 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p drag_modified.clear(); Map<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, mpos, mpos); for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - if (!erase_button->is_pressed() && E->get().source_id == -1) { + if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { continue; } Vector2i coords = E->key(); if (!drag_modified.has(coords)) { - drag_modified.insert(coords, TileMapCell(tile_map->get_cell_source_id(coords), tile_map->get_cell_atlas_coords(coords), tile_map->get_cell_alternative_tile(coords))); + drag_modified.insert(coords, tile_map->get_cell(coords)); } tile_map->set_cell(coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); } @@ -562,12 +551,12 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p if (!drag_modified.has(line[i])) { Map<Vector2i, TileMapCell> to_draw = _draw_bucket_fill(line[i], bucket_continuous_checkbox->is_pressed()); for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - if (!erase_button->is_pressed() && E->get().source_id == -1) { + if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { continue; } Vector2i coords = E->key(); if (!drag_modified.has(coords)) { - drag_modified.insert(coords, TileMapCell(tile_map->get_cell_source_id(coords), tile_map->get_cell_atlas_coords(coords), tile_map->get_cell_alternative_tile(coords))); + drag_modified.insert(coords, tile_map->get_cell(coords)); } tile_map->set_cell(coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); } @@ -634,7 +623,7 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over for (int x = rect.position.x; x < rect.get_end().x; x++) { for (int y = rect.position.y; y < rect.get_end().y; y++) { Vector2i coords = Vector2i(x, y); - if (tile_map->get_cell_source_id(coords) != -1) { + if (tile_map->get_cell_source_id(coords) != TileSet::INVALID_SOURCE) { Rect2 cell_region = xform.xform(Rect2(tile_map->map_to_world(coords) - tile_shape_size / 2, tile_shape_size)); tile_set->draw_tile_shape(p_overlay, cell_region, Color(1.0, 1.0, 1.0), false); } @@ -648,7 +637,7 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over for (int x = rect.position.x; x < rect.get_end().x; x++) { for (int y = rect.position.y; y < rect.get_end().y; y++) { Vector2i coords = Vector2i(x, y); - if (tile_map->get_cell_source_id(coords) != -1) { + if (tile_map->get_cell_source_id(coords) != TileSet::INVALID_SOURCE) { to_draw.insert(coords); } } @@ -871,7 +860,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_ // Get or create the pattern. TileMapPattern erase_pattern; - erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); + erase_pattern.set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern; Map<Vector2i, TileMapCell> output; @@ -923,7 +912,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start // Get or create the pattern. TileMapPattern erase_pattern; - erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); + erase_pattern.set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern; // Compute the offset to align things to the bottom or right. @@ -974,7 +963,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i // Get or create the pattern. TileMapPattern erase_pattern; - erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); + erase_pattern.set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern; Map<Vector2i, TileMapCell> output; @@ -983,7 +972,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i // If we are filling empty tiles, compute the tilemap boundaries. Rect2i boundaries; - if (source.source_id == -1) { + if (source.source_id == TileSet::INVALID_SOURCE) { boundaries = tile_map->get_used_rect(); } @@ -999,7 +988,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i if (source.source_id == tile_map->get_cell_source_id(coords) && source.get_atlas_coords() == tile_map->get_cell_atlas_coords(coords) && source.alternative_tile == tile_map->get_cell_alternative_tile(coords) && - (source.source_id != -1 || boundaries.has_point(coords))) { + (source.source_id != TileSet::INVALID_SOURCE || boundaries.has_point(coords))) { if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) { // Paint a random tile. output.insert(coords, _pick_random_tile(pattern)); @@ -1027,7 +1016,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i } else { // Replace all tiles like the source. TypedArray<Vector2i> to_check; - if (source.source_id == -1) { + if (source.source_id == TileSet::INVALID_SOURCE) { Rect2i rect = tile_map->get_used_rect(); if (rect.size.x <= 0 || rect.size.y <= 0) { rect = Rect2i(p_coords, Vector2i(1, 1)); @@ -1045,7 +1034,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i if (source.source_id == tile_map->get_cell_source_id(coords) && source.get_atlas_coords() == tile_map->get_cell_atlas_coords(coords) && source.alternative_tile == tile_map->get_cell_alternative_tile(coords) && - (source.source_id != -1 || boundaries.has_point(coords))) { + (source.source_id != TileSet::INVALID_SOURCE || boundaries.has_point(coords))) { if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) { // Paint a random tile. output.insert(coords, _pick_random_tile(pattern)); @@ -1102,7 +1091,7 @@ void TileMapEditorTilesPlugin::_stop_dragging() { tile_map_selection.erase(coords); } } else { - if (tile_map->get_cell_source_id(coords) != -1) { + if (tile_map->get_cell_source_id(coords) != TileSet::INVALID_SOURCE) { tile_map_selection.insert(coords); } } @@ -1173,7 +1162,7 @@ void TileMapEditorTilesPlugin::_stop_dragging() { for (int x = rect.position.x; x < rect.get_end().x; x++) { for (int y = rect.position.y; y < rect.get_end().y; y++) { Vector2i coords = Vector2i(x, y); - if (tile_map->get_cell_source_id(coords) != -1) { + if (tile_map->get_cell_source_id(coords) != TileSet::INVALID_SOURCE) { coords_array.push_back(coords); } } @@ -1198,7 +1187,7 @@ void TileMapEditorTilesPlugin::_stop_dragging() { Map<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, drag_start_mouse_pos, mpos); undo_redo->create_action(TTR("Paint tiles")); for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - if (!erase_button->is_pressed() && E->get().source_id == -1) { + if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { continue; } undo_redo->add_do_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); @@ -1210,7 +1199,7 @@ void TileMapEditorTilesPlugin::_stop_dragging() { Map<Vector2i, TileMapCell> to_draw = _draw_rect(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(mpos)); undo_redo->create_action(TTR("Paint tiles")); for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - if (!erase_button->is_pressed() && E->get().source_id == -1) { + if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { continue; } undo_redo->add_do_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); @@ -1221,7 +1210,7 @@ void TileMapEditorTilesPlugin::_stop_dragging() { case DRAG_TYPE_BUCKET: { undo_redo->create_action(TTR("Paint tiles")); for (Map<Vector2i, TileMapCell>::Element *E = drag_modified.front(); E; E = E->next()) { - if (!erase_button->is_pressed() && E->get().source_id == -1) { + if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { continue; } undo_redo->add_do_method(tile_map, "set_cell", E->key(), tile_map->get_cell_source_id(E->key()), tile_map->get_cell_atlas_coords(E->key()), tile_map->get_cell_alternative_tile(E->key())); @@ -1249,7 +1238,7 @@ void TileMapEditorTilesPlugin::_stop_dragging() { void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { - hovered_tile.source_id = -1; + hovered_tile.source_id = TileSet::INVALID_SOURCE; hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; tile_set_selection.clear(); @@ -1260,7 +1249,7 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() { Ref<TileSet> tile_set = tile_map->get_tileset(); if (!tile_set.is_valid()) { - hovered_tile.source_id = -1; + hovered_tile.source_id = TileSet::INVALID_SOURCE; hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; tile_set_selection.clear(); @@ -1271,7 +1260,7 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() { int source_index = sources_list->get_current(); if (source_index < 0 || source_index >= sources_list->get_item_count()) { - hovered_tile.source_id = -1; + hovered_tile.source_id = TileSet::INVALID_SOURCE; hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; tile_set_selection.clear(); @@ -1287,7 +1276,7 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() { !tile_set->has_source(hovered_tile.source_id) || !tile_set->get_source(hovered_tile.source_id)->has_tile(hovered_tile.get_atlas_coords()) || !tile_set->get_source(hovered_tile.source_id)->has_alternative_tile(hovered_tile.get_atlas_coords(), hovered_tile.alternative_tile)) { - hovered_tile.source_id = -1; + hovered_tile.source_id = TileSet::INVALID_SOURCE; hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; } @@ -1403,7 +1392,7 @@ void TileMapEditorTilesPlugin::_update_tileset_selection_from_selection_pattern( TypedArray<Vector2i> used_cells = selection_pattern->get_used_cells(); for (int i = 0; i < used_cells.size(); i++) { Vector2i coords = used_cells[i]; - if (selection_pattern->get_cell_source_id(coords) != -1) { + if (selection_pattern->get_cell_source_id(coords) != TileSet::INVALID_SOURCE) { tile_set_selection.insert(TileMapCell(selection_pattern->get_cell_source_id(coords), selection_pattern->get_cell_atlas_coords(coords), selection_pattern->get_cell_alternative_tile(coords))); } } @@ -1475,7 +1464,7 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_draw() { } void TileMapEditorTilesPlugin::_tile_atlas_control_mouse_exited() { - hovered_tile.source_id = -1; + hovered_tile.source_id = TileSet::INVALID_SOURCE; hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; tile_set_dragging_selection = false; @@ -1634,7 +1623,7 @@ void TileMapEditorTilesPlugin::_tile_alternatives_control_draw() { } void TileMapEditorTilesPlugin::_tile_alternatives_control_mouse_exited() { - hovered_tile.source_id = -1; + hovered_tile.source_id = TileSet::INVALID_SOURCE; hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; tile_set_dragging_selection = false; @@ -1856,19 +1845,6 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { _on_random_tile_checkbox_toggled(false); - // Wide empty separation control. - Control *h_empty_space = memnew(Control); - h_empty_space->set_h_size_flags(SIZE_EXPAND_FILL); - toolbar->add_child(h_empty_space); - - // Grid toggle. - toggle_grid_button = memnew(Button); - toggle_grid_button->set_flat(true); - toggle_grid_button->set_toggle_mode(true); - toggle_grid_button->set_tooltip(TTR("Toggle grid visibility.")); - toggle_grid_button->connect("toggled", callable_mp(this, &TileMapEditorTilesPlugin::_on_grid_toggled)); - toolbar->add_child(toggle_grid_button); - // Default tool. paint_tool_button->set_pressed(true); _update_toolbar(); @@ -1898,8 +1874,8 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { sources_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); sources_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_fix_selected_and_hovered).unbind(1)); sources_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_bottom_panel).unbind(1)); - sources_list->connect("item_selected", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_atlas_sources_lists_current)); - sources_list->connect("visibility_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::synchronize_atlas_sources_list), varray(sources_list)); + sources_list->connect("item_selected", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_sources_lists_current)); + sources_list->connect("visibility_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::synchronize_sources_list), varray(sources_list)); atlas_sources_split_container->add_child(sources_list); // Tile atlas source. @@ -2968,7 +2944,7 @@ void TileMapEditorTerrainsPlugin::_update_terrains_cache() { } TileMapCell empty_cell; - empty_cell.source_id = -1; + empty_cell.source_id = TileSet::INVALID_SOURCE; empty_cell.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); empty_cell.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; per_terrain_terrains_tile_patterns_tiles[i][empty_pattern].insert(empty_cell); @@ -3190,6 +3166,9 @@ void TileMapEditor::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: missing_tile_texture = get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons")); warning_pattern_texture = get_theme_icon(SNAME("WarningPattern"), SNAME("EditorIcons")); + advanced_menu_button->set_icon(get_theme_icon(SNAME("Tools"), SNAME("EditorIcons"))); + toggle_grid_button->set_icon(get_theme_icon(SNAME("Grid"), SNAME("EditorIcons"))); + toggle_grid_button->set_pressed(EditorSettings::get_singleton()->get("editors/tiles_editor/display_grid")); break; case NOTIFICATION_INTERNAL_PROCESS: if (is_visible_in_tree() && tileset_changed_needs_update) { @@ -3199,6 +3178,44 @@ void TileMapEditor::_notification(int p_what) { tileset_changed_needs_update = false; } break; + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: + toggle_grid_button->set_pressed(EditorSettings::get_singleton()->get("editors/tiles_editor/display_grid")); + break; + } +} + +void TileMapEditor::_on_grid_toggled(bool p_pressed) { + EditorSettings::get_singleton()->set("editors/tiles_editor/display_grid", p_pressed); +} + +void TileMapEditor::_advanced_menu_button_id_pressed(int p_id) { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + if (p_id == 0) { // Replace Tile Proxies + undo_redo->create_action(TTR("Replace Tiles with Proxies")); + TypedArray<Vector2i> used_cells = tile_map->get_used_cells(); + for (int i = 0; i < used_cells.size(); i++) { + Vector2i cell_coords = used_cells[i]; + TileMapCell from = tile_map->get_cell(cell_coords); + Array to_array = tile_set->map_tile_proxy(from.source_id, from.get_atlas_coords(), from.alternative_tile); + TileMapCell to; + to.source_id = to_array[0]; + to.set_atlas_coords(to_array[1]); + to.alternative_tile = to_array[2]; + if (from != to) { + undo_redo->add_do_method(tile_map, "set_cell", cell_coords, to.source_id, to.get_atlas_coords(), to.alternative_tile); + undo_redo->add_undo_method(tile_map, "set_cell", cell_coords, from.source_id, from.get_atlas_coords(), from.alternative_tile); + } + } + undo_redo->commit_action(); } } @@ -3349,7 +3366,6 @@ void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { Vector2i tile_shape_size = tile_set->get_tile_size(); // Draw tiles with invalid IDs in the grid. - float icon_ratio = MIN(missing_tile_texture->get_size().x / tile_set->get_tile_size().x, missing_tile_texture->get_size().y / tile_set->get_tile_size().y) / 3; TypedArray<Vector2i> used_cells = tile_map->get_used_cells(); for (int i = 0; i < used_cells.size(); i++) { Vector2i coords = used_cells[i]; @@ -3365,25 +3381,33 @@ void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { if (!source || !source->has_tile(tile_atlas_coords) || !source->has_alternative_tile(tile_atlas_coords, tile_alternative_tile)) { // Generate a random color from the hashed values of the tiles. - Array to_hash; - to_hash.push_back(tile_source_id); - to_hash.push_back(tile_atlas_coords); - to_hash.push_back(tile_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 the scaled tile. - Rect2 cell_region = xform.xform(Rect2(tile_map->map_to_world(coords) - Vector2(tile_shape_size) / 2, Vector2(tile_shape_size))); - tile_set->draw_tile_shape(p_overlay, cell_region, color, true, warning_pattern_texture); + Array a = tile_set->map_tile_proxy(tile_source_id, tile_atlas_coords, tile_alternative_tile); + if (int(a[0]) == tile_source_id && Vector2i(a[1]) == tile_atlas_coords && int(a[2]) == tile_alternative_tile) { + // Only display the pattern if we have no proxy tile. + Array to_hash; + to_hash.push_back(tile_source_id); + to_hash.push_back(tile_atlas_coords); + to_hash.push_back(tile_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 the scaled tile. + Rect2 cell_region = xform.xform(Rect2(tile_map->map_to_world(coords) - Vector2(tile_shape_size) / 2, Vector2(tile_shape_size))); + tile_set->draw_tile_shape(p_overlay, cell_region, color, true, warning_pattern_texture); + } // Draw the warning icon. - Rect2 rect = Rect2(xform.xform(tile_map->map_to_world(coords)) - (icon_ratio * missing_tile_texture->get_size() * xform.get_scale() / 2), icon_ratio * missing_tile_texture->get_size() * xform.get_scale()); + int min_axis = missing_tile_texture->get_size().min_axis(); + Vector2 icon_size; + icon_size[min_axis] = tile_set->get_tile_size()[min_axis] / 3; + icon_size[(min_axis + 1) % 2] = (icon_size[min_axis] * missing_tile_texture->get_size()[(min_axis + 1) % 2] / missing_tile_texture->get_size()[min_axis]); + Rect2 rect = Rect2(xform.xform(tile_map->map_to_world(coords)) - (icon_size * xform.get_scale() / 2), icon_size * xform.get_scale()); p_overlay->draw_texture_rect(missing_tile_texture, rect); } } @@ -3503,6 +3527,26 @@ TileMapEditor::TileMapEditor() { tilemap_toolbar->add_child(tile_map_editor_plugins[i]->get_toolbar()); } + // Wide empty separation control. + Control *h_empty_space = memnew(Control); + h_empty_space->set_h_size_flags(SIZE_EXPAND_FILL); + tilemap_toolbar->add_child(h_empty_space); + + // Grid toggle. + toggle_grid_button = memnew(Button); + toggle_grid_button->set_flat(true); + toggle_grid_button->set_toggle_mode(true); + toggle_grid_button->set_tooltip(TTR("Toggle grid visibility.")); + toggle_grid_button->connect("toggled", callable_mp(this, &TileMapEditor::_on_grid_toggled)); + tilemap_toolbar->add_child(toggle_grid_button); + + // Advanced settings menu button. + advanced_menu_button = memnew(MenuButton); + advanced_menu_button->set_flat(true); + advanced_menu_button->get_popup()->add_item(TTR("Automatically Replace Tiles with Proxies")); + advanced_menu_button->get_popup()->connect("id_pressed", callable_mp(this, &TileMapEditor::_advanced_menu_button_id_pressed)); + tilemap_toolbar->add_child(advanced_menu_button); + missing_tileset_label = memnew(Label); missing_tileset_label->set_text(TTR("The edited TileMap node has no TileSet resource.")); missing_tileset_label->set_h_size_flags(SIZE_EXPAND_FILL); diff --git a/editor/plugins/tiles/tile_map_editor.h b/editor/plugins/tiles/tile_map_editor.h index a6f4ec3021..236774a06b 100644 --- a/editor/plugins/tiles/tile_map_editor.h +++ b/editor/plugins/tiles/tile_map_editor.h @@ -82,9 +82,6 @@ private: void _on_random_tile_checkbox_toggled(bool p_pressed); void _on_scattering_spinbox_changed(double p_value); - Button *toggle_grid_button; - void _on_grid_toggled(bool p_pressed); - void _update_toolbar(); ///// Tilemap editing. ///// @@ -300,6 +297,7 @@ class TileMapEditor : public VBoxContainer { GDCLASS(TileMapEditor, VBoxContainer); private: + UndoRedo *undo_redo = EditorNode::get_undo_redo(); bool tileset_changed_needs_update = false; ObjectID tile_map_id; @@ -309,6 +307,12 @@ private: // Toolbar. HBoxContainer *tilemap_toolbar; + Button *toggle_grid_button; + void _on_grid_toggled(bool p_pressed); + + MenuButton *advanced_menu_button; + void _advanced_menu_button_id_pressed(int p_id); + // Bottom panel Label *missing_tileset_label; Tabs *tabs; diff --git a/editor/plugins/tiles/tile_proxies_manager_dialog.cpp b/editor/plugins/tiles/tile_proxies_manager_dialog.cpp new file mode 100644 index 0000000000..e5e7efa531 --- /dev/null +++ b/editor/plugins/tiles/tile_proxies_manager_dialog.cpp @@ -0,0 +1,476 @@ +/*************************************************************************/ +/* tile_proxies_manager_dialog.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 "tile_proxies_manager_dialog.h" + +#include "editor/editor_scale.h" + +void TileProxiesManagerDialog::_right_clicked(int p_item, Vector2 p_local_mouse_pos, Object *p_item_list) { + ItemList *item_list = Object::cast_to<ItemList>(p_item_list); + popup_menu->set_size(Vector2(1, 1)); + popup_menu->set_position(get_position() + item_list->get_global_mouse_position()); + popup_menu->popup(); +} + +void TileProxiesManagerDialog::_menu_id_pressed(int p_id) { + if (p_id == 0) { + // Delete. + _delete_selected_bindings(); + } +} + +void TileProxiesManagerDialog::_delete_selected_bindings() { + undo_redo->create_action("Remove Tile Proxies"); + + Vector<int> source_level_selected = source_level_list->get_selected_items(); + for (int i = 0; i < source_level_selected.size(); i++) { + int key = source_level_list->get_item_metadata(source_level_selected[i]); + int val = tile_set->get_source_level_tile_proxy(key); + undo_redo->add_do_method(*tile_set, "remove_source_level_tile_proxy", key); + undo_redo->add_undo_method(*tile_set, "set_source_level_tile_proxy", key, val); + } + + Vector<int> coords_level_selected = coords_level_list->get_selected_items(); + for (int i = 0; i < coords_level_selected.size(); i++) { + Array key = coords_level_list->get_item_metadata(coords_level_selected[i]); + Array val = tile_set->get_coords_level_tile_proxy(key[0], key[1]); + undo_redo->add_do_method(*tile_set, "remove_coords_level_tile_proxy", key[0], key[1]); + undo_redo->add_undo_method(*tile_set, "set_coords_level_tile_proxy", key[0], key[1], val[0], val[1]); + } + + Vector<int> alternative_level_selected = alternative_level_list->get_selected_items(); + for (int i = 0; i < alternative_level_selected.size(); i++) { + Array key = alternative_level_list->get_item_metadata(alternative_level_selected[i]); + Array val = tile_set->get_coords_level_tile_proxy(key[0], key[1]); + undo_redo->add_do_method(*tile_set, "remove_alternative_level_tile_proxy", key[0], key[1], key[2]); + undo_redo->add_undo_method(*tile_set, "set_alternative_level_tile_proxy", key[0], key[1], key[2], val[0], val[1], val[2]); + } + undo_redo->add_do_method(this, "_update_lists"); + undo_redo->add_undo_method(this, "_update_lists"); + undo_redo->commit_action(); + + commited_actions_count += 1; +} + +void TileProxiesManagerDialog::_update_lists() { + source_level_list->clear(); + coords_level_list->clear(); + alternative_level_list->clear(); + + Array proxies = tile_set->get_source_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + String text = vformat("%s", proxy[0]).rpad(5) + "-> " + vformat("%s", proxy[1]); + int id = source_level_list->add_item(text); + source_level_list->set_item_metadata(id, proxy[0]); + } + + proxies = tile_set->get_coords_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + String text = vformat("%s, %s", proxy[0], proxy[1]).rpad(17) + "-> " + vformat("%s, %s", proxy[2], proxy[3]); + int id = coords_level_list->add_item(text); + coords_level_list->set_item_metadata(id, proxy.slice(0, 2)); + } + + proxies = tile_set->get_alternative_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + String text = vformat("%s, %s, %s", proxy[0], proxy[1], proxy[2]).rpad(24) + "-> " + vformat("%s, %s, %s", proxy[3], proxy[4], proxy[5]); + int id = alternative_level_list->add_item(text); + alternative_level_list->set_item_metadata(id, proxy.slice(0, 3)); + } +} + +void TileProxiesManagerDialog::_update_enabled_property_editors() { + if (from.source_id == TileSet::INVALID_SOURCE) { + from.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); + to.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); + from.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; + to.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; + coords_from_property_editor->hide(); + coords_to_property_editor->hide(); + alternative_from_property_editor->hide(); + alternative_to_property_editor->hide(); + } else if (from.get_atlas_coords().x == -1 || from.get_atlas_coords().y == -1) { + from.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; + to.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; + coords_from_property_editor->show(); + coords_to_property_editor->show(); + alternative_from_property_editor->hide(); + alternative_to_property_editor->hide(); + } else { + coords_from_property_editor->show(); + coords_to_property_editor->show(); + alternative_from_property_editor->show(); + alternative_to_property_editor->show(); + } + + source_from_property_editor->update_property(); + source_to_property_editor->update_property(); + coords_from_property_editor->update_property(); + coords_to_property_editor->update_property(); + alternative_from_property_editor->update_property(); + alternative_to_property_editor->update_property(); +} + +void TileProxiesManagerDialog::_property_changed(const String &p_path, const Variant &p_value, const String &p_name, bool p_changing) { + _set(p_path, p_value); +} + +void TileProxiesManagerDialog::_add_button_pressed() { + if (from.source_id != TileSet::INVALID_SOURCE && to.source_id != TileSet::INVALID_SOURCE) { + Vector2i from_coords = from.get_atlas_coords(); + Vector2i to_coords = to.get_atlas_coords(); + if (from_coords.x >= 0 && from_coords.y >= 0 && to_coords.x >= 0 && to_coords.y >= 0) { + if (from.alternative_tile != TileSetSource::INVALID_TILE_ALTERNATIVE && to.alternative_tile != TileSetSource::INVALID_TILE_ALTERNATIVE) { + undo_redo->create_action("Create Alternative-level Tile Proxy"); + undo_redo->add_do_method(*tile_set, "set_alternative_level_tile_proxy", from.source_id, from.get_atlas_coords(), from.alternative_tile, to.source_id, to.get_atlas_coords(), to.alternative_tile); + if (tile_set->has_alternative_level_tile_proxy(from.source_id, from.get_atlas_coords(), from.alternative_tile)) { + Array a = tile_set->get_alternative_level_tile_proxy(from.source_id, from.get_atlas_coords(), from.alternative_tile); + undo_redo->add_undo_method(*tile_set, "set_alternative_level_tile_proxy", to.source_id, to.get_atlas_coords(), to.alternative_tile, a[0], a[1], a[2]); + } else { + undo_redo->add_undo_method(*tile_set, "remove_alternative_level_tile_proxy", from.source_id, from.get_atlas_coords(), from.alternative_tile); + } + } else { + undo_redo->create_action("Create Coords-level Tile Proxy"); + undo_redo->add_do_method(*tile_set, "set_coords_level_tile_proxy", from.source_id, from.get_atlas_coords(), to.source_id, to.get_atlas_coords()); + if (tile_set->has_coords_level_tile_proxy(from.source_id, from.get_atlas_coords())) { + Array a = tile_set->get_coords_level_tile_proxy(from.source_id, from.get_atlas_coords()); + undo_redo->add_undo_method(*tile_set, "set_coords_level_tile_proxy", to.source_id, to.get_atlas_coords(), a[0], a[1]); + } else { + undo_redo->add_undo_method(*tile_set, "remove_coords_level_tile_proxy", from.source_id, from.get_atlas_coords()); + } + } + } else { + undo_redo->create_action("Create source-level Tile Proxy"); + undo_redo->add_do_method(*tile_set, "set_source_level_tile_proxy", from.source_id, to.source_id); + if (tile_set->has_source_level_tile_proxy(from.source_id)) { + undo_redo->add_undo_method(*tile_set, "set_source_level_tile_proxy", to.source_id, tile_set->get_source_level_tile_proxy(from.source_id)); + } else { + undo_redo->add_undo_method(*tile_set, "remove_source_level_tile_proxy", from.source_id); + } + } + undo_redo->add_do_method(this, "_update_lists"); + undo_redo->add_undo_method(this, "_update_lists"); + undo_redo->commit_action(); + commited_actions_count++; + } +} + +void TileProxiesManagerDialog::_clear_invalid_button_pressed() { + undo_redo->create_action("Delete All Invalid Tile Proxies"); + + undo_redo->add_do_method(*tile_set, "cleanup_invalid_tile_proxies"); + + Array proxies = tile_set->get_source_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + undo_redo->add_undo_method(*tile_set, "set_source_level_tile_proxy", proxy[0], proxy[1]); + } + + proxies = tile_set->get_coords_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + undo_redo->add_undo_method(*tile_set, "set_coords_level_tile_proxy", proxy[0], proxy[1], proxy[2], proxy[3]); + } + + proxies = tile_set->get_alternative_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + undo_redo->add_undo_method(*tile_set, "set_alternative_level_tile_proxy", proxy[0], proxy[1], proxy[2], proxy[3], proxy[4], proxy[5]); + } + undo_redo->add_do_method(this, "_update_lists"); + undo_redo->add_undo_method(this, "_update_lists"); + undo_redo->commit_action(); +} + +void TileProxiesManagerDialog::_clear_all_button_pressed() { + undo_redo->create_action("Delete All Tile Proxies"); + + undo_redo->add_do_method(*tile_set, "clear_tile_proxies"); + + Array proxies = tile_set->get_source_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + undo_redo->add_undo_method(*tile_set, "set_source_level_tile_proxy", proxy[0], proxy[1]); + } + + proxies = tile_set->get_coords_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + undo_redo->add_undo_method(*tile_set, "set_coords_level_tile_proxy", proxy[0], proxy[1], proxy[2], proxy[3]); + } + + proxies = tile_set->get_alternative_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + undo_redo->add_undo_method(*tile_set, "set_alternative_level_tile_proxy", proxy[0], proxy[1], proxy[2], proxy[3], proxy[4], proxy[5]); + } + undo_redo->add_do_method(this, "_update_lists"); + undo_redo->add_undo_method(this, "_update_lists"); + undo_redo->commit_action(); +} + +bool TileProxiesManagerDialog::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "from_source") { + from.source_id = MAX(int(p_value), -1); + } else if (p_name == "from_coords") { + from.set_atlas_coords(Vector2i(p_value).max(Vector2i(-1, -1))); + } else if (p_name == "from_alternative") { + from.alternative_tile = MAX(int(p_value), -1); + } else if (p_name == "to_source") { + to.source_id = MAX(int(p_value), 0); + } else if (p_name == "to_coords") { + to.set_atlas_coords(Vector2i(p_value).max(Vector2i(0, 0))); + } else if (p_name == "to_alternative") { + to.alternative_tile = MAX(int(p_value), 0); + } else { + return false; + } + _update_enabled_property_editors(); + return true; +} + +bool TileProxiesManagerDialog::_get(const StringName &p_name, Variant &r_ret) const { + if (p_name == "from_source") { + r_ret = from.source_id; + } else if (p_name == "from_coords") { + r_ret = from.get_atlas_coords(); + } else if (p_name == "from_alternative") { + r_ret = from.alternative_tile; + } else if (p_name == "to_source") { + r_ret = to.source_id; + } else if (p_name == "to_coords") { + r_ret = to.get_atlas_coords(); + } else if (p_name == "to_alternative") { + r_ret = to.alternative_tile; + } else { + return false; + } + return true; +} + +void TileProxiesManagerDialog::_unhandled_key_input(Ref<InputEvent> p_event) { + ERR_FAIL_COND(p_event.is_null()); + + if (p_event->is_pressed() && !p_event->is_echo() && (Object::cast_to<InputEventKey>(p_event.ptr()) || Object::cast_to<InputEventJoypadButton>(p_event.ptr()) || Object::cast_to<InputEventAction>(*p_event))) { + if (!is_inside_tree() || !is_visible()) { + return; + } + + if (popup_menu->activate_item_by_event(p_event, false)) { + set_input_as_handled(); + } + } +} + +void TileProxiesManagerDialog::cancel_pressed() { + for (int i = 0; i < commited_actions_count; i++) { + undo_redo->undo(); + } + commited_actions_count = 0; +} + +void TileProxiesManagerDialog::_bind_methods() { + ClassDB::bind_method(D_METHOD("_update_lists"), &TileProxiesManagerDialog::_update_lists); + ClassDB::bind_method(D_METHOD("_unhandled_key_input"), &TileProxiesManagerDialog::_unhandled_key_input); +} + +void TileProxiesManagerDialog::update_tile_set(Ref<TileSet> p_tile_set) { + ERR_FAIL_COND(!p_tile_set.is_valid()); + tile_set = p_tile_set; + commited_actions_count = 0; + _update_lists(); +} + +TileProxiesManagerDialog::TileProxiesManagerDialog() { + // Tile proxy management window. + set_title(TTR("Tile Proxies Management")); + set_process_unhandled_key_input(true); + + to.source_id = 0; + to.set_atlas_coords(Vector2i()); + to.alternative_tile = 0; + + VBoxContainer *vbox_container = memnew(VBoxContainer); + vbox_container->set_h_size_flags(Control::SIZE_EXPAND_FILL); + vbox_container->set_v_size_flags(Control::SIZE_EXPAND_FILL); + add_child(vbox_container); + + Label *source_level_label = memnew(Label); + source_level_label->set_text(TTR("Source-level proxies")); + vbox_container->add_child(source_level_label); + + source_level_list = memnew(ItemList); + source_level_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); + source_level_list->set_select_mode(ItemList::SELECT_MULTI); + source_level_list->set_allow_rmb_select(true); + source_level_list->connect("item_rmb_selected", callable_mp(this, &TileProxiesManagerDialog::_right_clicked), varray(source_level_list)); + vbox_container->add_child(source_level_list); + + Label *coords_level_label = memnew(Label); + coords_level_label->set_text(TTR("Coords-level proxies")); + vbox_container->add_child(coords_level_label); + + coords_level_list = memnew(ItemList); + coords_level_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); + coords_level_list->set_select_mode(ItemList::SELECT_MULTI); + coords_level_list->set_allow_rmb_select(true); + coords_level_list->connect("item_rmb_selected", callable_mp(this, &TileProxiesManagerDialog::_right_clicked), varray(coords_level_list)); + vbox_container->add_child(coords_level_list); + + Label *alternative_level_label = memnew(Label); + alternative_level_label->set_text(TTR("Alternative-level proxies")); + vbox_container->add_child(alternative_level_label); + + alternative_level_list = memnew(ItemList); + alternative_level_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); + alternative_level_list->set_select_mode(ItemList::SELECT_MULTI); + alternative_level_list->set_allow_rmb_select(true); + alternative_level_list->connect("item_rmb_selected", callable_mp(this, &TileProxiesManagerDialog::_right_clicked), varray(alternative_level_list)); + vbox_container->add_child(alternative_level_list); + + popup_menu = memnew(PopupMenu); + popup_menu->add_shortcut(ED_GET_SHORTCUT("ui_text_delete")); + popup_menu->connect("id_pressed", callable_mp(this, &TileProxiesManagerDialog::_menu_id_pressed)); + add_child(popup_menu); + + // Add proxy panel. + HSeparator *h_separator = memnew(HSeparator); + vbox_container->add_child(h_separator); + + Label *add_label = memnew(Label); + add_label->set_text(TTR("Add a new tile proxy:")); + vbox_container->add_child(add_label); + + HBoxContainer *hboxcontainer = memnew(HBoxContainer); + vbox_container->add_child(hboxcontainer); + + // From + VBoxContainer *vboxcontainer_from = memnew(VBoxContainer); + vboxcontainer_from->set_h_size_flags(Control::SIZE_EXPAND_FILL); + hboxcontainer->add_child(vboxcontainer_from); + + source_from_property_editor = memnew(EditorPropertyInteger); + source_from_property_editor->set_label(TTR("From Source")); + source_from_property_editor->set_object_and_property(this, "from_source"); + source_from_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed)); + source_from_property_editor->set_selectable(false); + source_from_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); + source_from_property_editor->setup(-1, 99999, 1, true, false); + vboxcontainer_from->add_child(source_from_property_editor); + + coords_from_property_editor = memnew(EditorPropertyVector2i); + coords_from_property_editor->set_label(TTR("From Coords")); + coords_from_property_editor->set_object_and_property(this, "from_coords"); + coords_from_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed)); + coords_from_property_editor->set_selectable(false); + coords_from_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); + coords_from_property_editor->setup(-1, 99999, true); + coords_from_property_editor->hide(); + vboxcontainer_from->add_child(coords_from_property_editor); + + alternative_from_property_editor = memnew(EditorPropertyInteger); + alternative_from_property_editor->set_label(TTR("From Alternative")); + alternative_from_property_editor->set_object_and_property(this, "from_alternative"); + alternative_from_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed)); + alternative_from_property_editor->set_selectable(false); + alternative_from_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); + alternative_from_property_editor->setup(-1, 99999, 1, true, false); + alternative_from_property_editor->hide(); + vboxcontainer_from->add_child(alternative_from_property_editor); + + // To + VBoxContainer *vboxcontainer_to = memnew(VBoxContainer); + vboxcontainer_to->set_h_size_flags(Control::SIZE_EXPAND_FILL); + hboxcontainer->add_child(vboxcontainer_to); + + source_to_property_editor = memnew(EditorPropertyInteger); + source_to_property_editor->set_label(TTR("To Source")); + source_to_property_editor->set_object_and_property(this, "to_source"); + source_to_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed)); + source_to_property_editor->set_selectable(false); + source_to_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); + source_to_property_editor->setup(-1, 99999, 1, true, false); + vboxcontainer_to->add_child(source_to_property_editor); + + coords_to_property_editor = memnew(EditorPropertyVector2i); + coords_to_property_editor->set_label(TTR("To Coords")); + coords_to_property_editor->set_object_and_property(this, "to_coords"); + coords_to_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed)); + coords_to_property_editor->set_selectable(false); + coords_to_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); + coords_to_property_editor->setup(-1, 99999, true); + coords_to_property_editor->hide(); + vboxcontainer_to->add_child(coords_to_property_editor); + + alternative_to_property_editor = memnew(EditorPropertyInteger); + alternative_to_property_editor->set_label(TTR("To Alternative")); + alternative_to_property_editor->set_object_and_property(this, "to_alternative"); + alternative_to_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed)); + alternative_to_property_editor->set_selectable(false); + alternative_to_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); + alternative_to_property_editor->setup(-1, 99999, 1, true, false); + alternative_to_property_editor->hide(); + vboxcontainer_to->add_child(alternative_to_property_editor); + + Button *add_button = memnew(Button); + add_button->set_text(TTR("Add")); + add_button->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + add_button->connect("pressed", callable_mp(this, &TileProxiesManagerDialog::_add_button_pressed)); + vbox_container->add_child(add_button); + + h_separator = memnew(HSeparator); + vbox_container->add_child(h_separator); + + // Generic actions. + Label *generic_actions_label = memnew(Label); + generic_actions_label->set_text(TTR("Global actions:")); + vbox_container->add_child(generic_actions_label); + + hboxcontainer = memnew(HBoxContainer); + vbox_container->add_child(hboxcontainer); + + Button *clear_invalid_button = memnew(Button); + clear_invalid_button->set_text(TTR("Clear Invalid")); + clear_invalid_button->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + clear_invalid_button->connect("pressed", callable_mp(this, &TileProxiesManagerDialog::_clear_invalid_button_pressed)); + hboxcontainer->add_child(clear_invalid_button); + + Button *clear_all_button = memnew(Button); + clear_all_button->set_text(TTR("Clear All")); + clear_all_button->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + clear_all_button->connect("pressed", callable_mp(this, &TileProxiesManagerDialog::_clear_all_button_pressed)); + hboxcontainer->add_child(clear_all_button); + + h_separator = memnew(HSeparator); + vbox_container->add_child(h_separator); +} diff --git a/editor/plugins/tiles/tile_proxies_manager_dialog.h b/editor/plugins/tiles/tile_proxies_manager_dialog.h new file mode 100644 index 0000000000..f6898e960b --- /dev/null +++ b/editor/plugins/tiles/tile_proxies_manager_dialog.h @@ -0,0 +1,90 @@ +/*************************************************************************/ +/* tile_proxies_manager_dialog.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TILE_PROXIES_MANAGER_DIALOG_H +#define TILE_PROXIES_MANAGER_DIALOG_H + +#include "editor/editor_node.h" +#include "editor/editor_properties.h" + +#include "scene/gui/dialogs.h" +#include "scene/gui/item_list.h" +#include "scene/resources/tile_set.h" + +class TileProxiesManagerDialog : public ConfirmationDialog { + GDCLASS(TileProxiesManagerDialog, ConfirmationDialog); + +private: + int commited_actions_count = 0; + Ref<TileSet> tile_set; + + UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + + TileMapCell from; + TileMapCell to; + + // GUI + ItemList *source_level_list; + ItemList *coords_level_list; + ItemList *alternative_level_list; + + EditorPropertyInteger *source_from_property_editor; + EditorPropertyVector2i *coords_from_property_editor; + EditorPropertyInteger *alternative_from_property_editor; + EditorPropertyInteger *source_to_property_editor; + EditorPropertyVector2i *coords_to_property_editor; + EditorPropertyInteger *alternative_to_property_editor; + + PopupMenu *popup_menu; + void _right_clicked(int p_item, Vector2 p_local_mouse_pos, Object *p_item_list); + void _menu_id_pressed(int p_id); + void _delete_selected_bindings(); + void _update_lists(); + void _update_enabled_property_editors(); + void _property_changed(const String &p_path, const Variant &p_value, const String &p_name, bool p_changing); + void _add_button_pressed(); + + void _clear_invalid_button_pressed(); + void _clear_all_button_pressed(); + +protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _unhandled_key_input(Ref<InputEvent> p_event); + virtual void cancel_pressed() override; + static void _bind_methods(); + +public: + void update_tile_set(Ref<TileSet> p_tile_set); + + TileProxiesManagerDialog(); +}; + +#endif // TILE_PROXIES_MANAGER_DIALOG_H diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index 6b617cf4d8..de910dfd32 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -745,7 +745,7 @@ void TileSetAtlasSourceEditor::_update_atlas_view() { button->add_theme_style_override("hover", memnew(StyleBoxEmpty)); button->add_theme_style_override("focus", memnew(StyleBoxEmpty)); button->add_theme_style_override("pressed", memnew(StyleBoxEmpty)); - button->connect("pressed", callable_mp(tile_set_atlas_source, &TileSetAtlasSource::create_alternative_tile), varray(tile_id, -1)); + button->connect("pressed", callable_mp(tile_set_atlas_source, &TileSetAtlasSource::create_alternative_tile), varray(tile_id, TileSetSource::INVALID_TILE_ALTERNATIVE)); button->set_rect(Rect2(Vector2(pos.x, pos.y + (y_increment - texture_region_base_size.y) / 2.0), Vector2(texture_region_base_size_min, texture_region_base_size_min))); button->set_expand_icon(true); diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.h b/editor/plugins/tiles/tile_set_atlas_source_editor.h index dbb0756a16..501416c340 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.h +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.h @@ -65,7 +65,7 @@ private: private: Ref<TileSet> tile_set; TileSetAtlasSource *tile_set_atlas_source = nullptr; - int source_id = -1; + int source_id = TileSet::INVALID_SOURCE; protected: bool _set(const StringName &p_name, const Variant &p_value); @@ -108,7 +108,7 @@ private: Ref<TileSet> tile_set; TileSetAtlasSource *tile_set_atlas_source = nullptr; - int tile_set_atlas_source_id = -1; + int tile_set_atlas_source_id = TileSet::INVALID_SOURCE; UndoRedo *undo_redo = EditorNode::get_undo_redo(); diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp index 76ce6f0065..ba98a7d6b3 100644 --- a/editor/plugins/tiles/tile_set_editor.cpp +++ b/editor/plugins/tiles/tile_set_editor.cpp @@ -50,7 +50,7 @@ void TileSetEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, C if (p_from == sources_list) { // Handle dropping a texture in the list of atlas resources. - int source_id = -1; + int source_id = TileSet::INVALID_SOURCE; int added = 0; Dictionary d = p_data; Vector<String> files = d["files"]; @@ -77,7 +77,7 @@ void TileSetEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, C } // Update the selected source (thus triggering an update). - _update_atlas_sources_list(source_id); + _update_sources_list(source_id); } } @@ -114,11 +114,11 @@ bool TileSetEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_dat return false; } -void TileSetEditor::_update_atlas_sources_list(int force_selected_id) { +void TileSetEditor::_update_sources_list(int force_selected_id) { ERR_FAIL_COND(!tile_set.is_valid()); // Get the previously selected id. - int old_selected = -1; + int old_selected = TileSet::INVALID_SOURCE; if (sources_list->get_current() >= 0) { int source_id = sources_list->get_item_metadata(sources_list->get_current()); if (tile_set->has_source(source_id)) { @@ -126,7 +126,7 @@ void TileSetEditor::_update_atlas_sources_list(int force_selected_id) { } } - int to_select = -1; + int to_select = TileSet::INVALID_SOURCE; if (force_selected_id >= 0) { to_select = force_selected_id; } else if (old_selected >= 0) { @@ -200,7 +200,7 @@ void TileSetEditor::_update_atlas_sources_list(int force_selected_id) { _source_selected(sources_list->get_current()); // Synchronize the lists. - TilesEditor::get_singleton()->set_atlas_sources_lists_current(sources_list->get_current()); + TilesEditor::get_singleton()->set_sources_lists_current(sources_list->get_current()); } void TileSetEditor::_source_selected(int p_source_index) { @@ -235,6 +235,23 @@ void TileSetEditor::_source_selected(int p_source_index) { } } +void TileSetEditor::_source_delete_pressed() { + ERR_FAIL_COND(!tile_set.is_valid()); + + // Update the selected source. + int to_delete = sources_list->get_item_metadata(sources_list->get_current()); + + Ref<TileSetSource> source = tile_set->get_source(to_delete); + + // Remove the source. + undo_redo->create_action(TTR("Remove source")); + undo_redo->add_do_method(*tile_set, "remove_source", to_delete); + undo_redo->add_undo_method(*tile_set, "add_source", source, to_delete); + undo_redo->commit_action(); + + _update_sources_list(); +} + void TileSetEditor::_source_add_id_pressed(int p_id_pressed) { ERR_FAIL_COND(!tile_set.is_valid()); @@ -251,7 +268,7 @@ void TileSetEditor::_source_add_id_pressed(int p_id_pressed) { undo_redo->add_undo_method(*tile_set, "remove_source", source_id); undo_redo->commit_action(); - _update_atlas_sources_list(source_id); + _update_sources_list(source_id); } break; case 1: { int source_id = tile_set->get_next_source_id(); @@ -264,28 +281,26 @@ void TileSetEditor::_source_add_id_pressed(int p_id_pressed) { undo_redo->add_undo_method(*tile_set, "remove_source", source_id); undo_redo->commit_action(); - _update_atlas_sources_list(source_id); + _update_sources_list(source_id); } break; default: ERR_FAIL(); } } -void TileSetEditor::_source_delete_pressed() { +void TileSetEditor::_sources_advanced_menu_id_pressed(int p_id_pressed) { ERR_FAIL_COND(!tile_set.is_valid()); - // Update the selected source. - int to_delete = sources_list->get_item_metadata(sources_list->get_current()); - - Ref<TileSetSource> source = tile_set->get_source(to_delete); - - // Remove the source. - undo_redo->create_action(TTR("Remove source")); - undo_redo->add_do_method(*tile_set, "remove_source", to_delete); - undo_redo->add_undo_method(*tile_set, "add_source", source, to_delete); - undo_redo->commit_action(); - - _update_atlas_sources_list(); + switch (p_id_pressed) { + case 0: { + atlas_merging_dialog->update_tile_set(tile_set); + atlas_merging_dialog->popup_centered_ratio(0.5); + } break; + case 1: { + tile_proxies_manager_dialog->update_tile_set(tile_set); + tile_proxies_manager_dialog->popup_centered_ratio(0.5); + } break; + } } void TileSetEditor::_notification(int p_what) { @@ -294,6 +309,7 @@ void TileSetEditor::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: sources_delete_button->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); sources_add_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + sources_advanced_menu_button->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); missing_texture_texture = get_theme_icon(SNAME("TileSet"), SNAME("EditorIcons")); break; case NOTIFICATION_INTERNAL_PROCESS: @@ -301,7 +317,7 @@ void TileSetEditor::_notification(int p_what) { if (tile_set.is_valid()) { tile_set->set_edited(true); } - _update_atlas_sources_list(); + _update_sources_list(); tile_set_changed_needs_update = false; } break; @@ -414,7 +430,7 @@ void TileSetEditor::edit(Ref<TileSet> p_tile_set) { // Add the listener again. if (tile_set.is_valid()) { tile_set->connect("changed", callable_mp(this, &TileSetEditor::_tile_set_changed)); - _update_atlas_sources_list(); + _update_sources_list(); } tile_set_atlas_source_editor->hide(); @@ -447,8 +463,8 @@ TileSetEditor::TileSetEditor() { sources_list->set_h_size_flags(SIZE_EXPAND_FILL); sources_list->set_v_size_flags(SIZE_EXPAND_FILL); sources_list->connect("item_selected", callable_mp(this, &TileSetEditor::_source_selected)); - sources_list->connect("item_selected", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_atlas_sources_lists_current)); - sources_list->connect("visibility_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::synchronize_atlas_sources_list), varray(sources_list)); + sources_list->connect("item_selected", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_sources_lists_current)); + sources_list->connect("visibility_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::synchronize_sources_list), varray(sources_list)); sources_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); sources_list->set_drag_forwarding(this); split_container_left_side->add_child(sources_list); @@ -470,6 +486,19 @@ TileSetEditor::TileSetEditor() { sources_add_button->get_popup()->connect("id_pressed", callable_mp(this, &TileSetEditor::_source_add_id_pressed)); sources_bottom_actions->add_child(sources_add_button); + sources_advanced_menu_button = memnew(MenuButton); + sources_advanced_menu_button->set_flat(true); + sources_advanced_menu_button->get_popup()->add_item(TTR("Open Atlas Merging Tool")); + sources_advanced_menu_button->get_popup()->add_item(TTR("Manage Tile Proxies")); + sources_advanced_menu_button->get_popup()->connect("id_pressed", callable_mp(this, &TileSetEditor::_sources_advanced_menu_id_pressed)); + sources_bottom_actions->add_child(sources_advanced_menu_button); + + atlas_merging_dialog = memnew(AtlasMergingDialog); + add_child(atlas_merging_dialog); + + tile_proxies_manager_dialog = memnew(TileProxiesManagerDialog); + add_child(tile_proxies_manager_dialog); + // Right side container. VBoxContainer *split_container_right_side = memnew(VBoxContainer); split_container_right_side->set_h_size_flags(SIZE_EXPAND_FILL); @@ -489,7 +518,7 @@ TileSetEditor::TileSetEditor() { tile_set_atlas_source_editor = memnew(TileSetAtlasSourceEditor); tile_set_atlas_source_editor->set_h_size_flags(SIZE_EXPAND_FILL); tile_set_atlas_source_editor->set_v_size_flags(SIZE_EXPAND_FILL); - tile_set_atlas_source_editor->connect("source_id_changed", callable_mp(this, &TileSetEditor::_update_atlas_sources_list)); + tile_set_atlas_source_editor->connect("source_id_changed", callable_mp(this, &TileSetEditor::_update_sources_list)); split_container_right_side->add_child(tile_set_atlas_source_editor); tile_set_atlas_source_editor->hide(); @@ -497,7 +526,7 @@ TileSetEditor::TileSetEditor() { tile_set_scenes_collection_source_editor = memnew(TileSetScenesCollectionSourceEditor); tile_set_scenes_collection_source_editor->set_h_size_flags(SIZE_EXPAND_FILL); tile_set_scenes_collection_source_editor->set_v_size_flags(SIZE_EXPAND_FILL); - tile_set_scenes_collection_source_editor->connect("source_id_changed", callable_mp(this, &TileSetEditor::_update_atlas_sources_list)); + tile_set_scenes_collection_source_editor->connect("source_id_changed", callable_mp(this, &TileSetEditor::_update_sources_list)); split_container_right_side->add_child(tile_set_scenes_collection_source_editor); tile_set_scenes_collection_source_editor->hide(); diff --git a/editor/plugins/tiles/tile_set_editor.h b/editor/plugins/tiles/tile_set_editor.h index 9e50aca62f..970e3fabb6 100644 --- a/editor/plugins/tiles/tile_set_editor.h +++ b/editor/plugins/tiles/tile_set_editor.h @@ -31,8 +31,10 @@ #ifndef TILE_SET_EDITOR_H #define TILE_SET_EDITOR_H +#include "atlas_merging_dialog.h" #include "scene/gui/box_container.h" #include "scene/resources/tile_set.h" +#include "tile_proxies_manager_dialog.h" #include "tile_set_atlas_source_editor.h" #include "tile_set_scenes_collection_source_editor.h" @@ -51,16 +53,21 @@ private: UndoRedo *undo_redo = EditorNode::get_undo_redo(); - void _update_atlas_sources_list(int force_selected_id = -1); + void _update_sources_list(int force_selected_id = -1); - // -- Sources management -- + // Sources management. Button *sources_delete_button; MenuButton *sources_add_button; + MenuButton *sources_advanced_menu_button; ItemList *sources_list; Ref<Texture2D> missing_texture_texture; void _source_selected(int p_source_index); - void _source_add_id_pressed(int p_id_pressed); void _source_delete_pressed(); + void _source_add_id_pressed(int p_id_pressed); + void _sources_advanced_menu_id_pressed(int p_id_pressed); + + AtlasMergingDialog *atlas_merging_dialog; + TileProxiesManagerDialog *tile_proxies_manager_dialog; void _tile_set_changed(); diff --git a/editor/plugins/tiles/tiles_editor_plugin.cpp b/editor/plugins/tiles/tiles_editor_plugin.cpp index 339efc7b99..79b869b511 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.cpp +++ b/editor/plugins/tiles/tiles_editor_plugin.cpp @@ -118,11 +118,11 @@ void TilesEditor::_update_editors() { CanvasItemEditor::get_singleton()->update_viewport(); } -void TilesEditor::set_atlas_sources_lists_current(int p_current) { +void TilesEditor::set_sources_lists_current(int p_current) { atlas_sources_lists_current = p_current; } -void TilesEditor::synchronize_atlas_sources_list(Object *p_current) { +void TilesEditor::synchronize_sources_list(Object *p_current) { ItemList *item_list = Object::cast_to<ItemList>(p_current); ERR_FAIL_COND(!item_list); diff --git a/editor/plugins/tiles/tiles_editor_plugin.h b/editor/plugins/tiles/tiles_editor_plugin.h index 6cc6f51598..f976d68938 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.h +++ b/editor/plugins/tiles/tiles_editor_plugin.h @@ -76,8 +76,8 @@ public: void forward_canvas_draw_over_viewport(Control *p_overlay) { tilemap_editor->forward_canvas_draw_over_viewport(p_overlay); } // To synchronize the atlas sources lists. - void set_atlas_sources_lists_current(int p_current); - void synchronize_atlas_sources_list(Object *p_current); + void set_sources_lists_current(int p_current); + void synchronize_sources_list(Object *p_current); void set_atlas_view_transform(float p_zoom, Vector2 p_scroll); void synchronize_atlas_view(Object *p_current); diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 46a1253ca0..447fe6424f 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -494,13 +494,13 @@ private: if (!f) { set_message(TTR("Couldn't create project.godot in project path."), MESSAGE_ERROR); } else { - f->store_line("[gd_resource type=\"Environment\" load_steps=2 format=2]"); + f->store_line("[gd_resource type=\"Environment\" load_steps=2 format=3]"); f->store_line(""); - f->store_line("[sub_resource type=\"Sky\" id=1]"); + f->store_line("[sub_resource type=\"Sky\" id=\"1\"]"); f->store_line(""); f->store_line("[resource]"); f->store_line("background_mode = 2"); - f->store_line("sky = SubResource( 1 )"); + f->store_line("sky = SubResource( \"1\" )"); memdelete(f); } } diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 6d1deb86c6..f453f4102d 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -1384,30 +1384,25 @@ void SceneTreeDock::_set_owners(Node *p_owner, const Array &p_nodes) { } } -void SceneTreeDock::_fill_path_renames(Vector<StringName> base_path, Vector<StringName> new_base_path, Node *p_node, List<Pair<NodePath, NodePath>> *p_renames) { +void SceneTreeDock::_fill_path_renames(Vector<StringName> base_path, Vector<StringName> new_base_path, Node *p_node, Map<Node *, NodePath> *p_renames) { base_path.push_back(p_node->get_name()); if (new_base_path.size()) { new_base_path.push_back(p_node->get_name()); } - NodePath from(base_path, true); - NodePath to; + NodePath new_path; if (new_base_path.size()) { - to = NodePath(new_base_path, true); + new_path = NodePath(new_base_path, true); } - Pair<NodePath, NodePath> npp; - npp.first = from; - npp.second = to; - - p_renames->push_back(npp); + p_renames->insert(p_node, new_path); for (int i = 0; i < p_node->get_child_count(); i++) { _fill_path_renames(base_path, new_base_path, p_node->get_child(i), p_renames); } } -void SceneTreeDock::fill_path_renames(Node *p_node, Node *p_new_parent, List<Pair<NodePath, NodePath>> *p_renames) { +void SceneTreeDock::fill_path_renames(Node *p_node, Node *p_new_parent, Map<Node *, NodePath> *p_renames) { Vector<StringName> base_path; Node *n = p_node->get_parent(); while (n) { @@ -1430,50 +1425,41 @@ void SceneTreeDock::fill_path_renames(Node *p_node, Node *p_new_parent, List<Pai _fill_path_renames(base_path, new_base_path, p_node, p_renames); } -bool SceneTreeDock::_update_node_path(const NodePath &p_root_path, NodePath &r_node_path, List<Pair<NodePath, NodePath>> *p_renames) { - NodePath root_path_new = p_root_path; - for (List<Pair<NodePath, NodePath>>::Element *F = p_renames->front(); F; F = F->next()) { - if (p_root_path == F->get().first) { - root_path_new = F->get().second; - break; - } - } - - // Goes through all paths to check if it's matching. - for (List<Pair<NodePath, NodePath>>::Element *F = p_renames->front(); F; F = F->next()) { - NodePath rel_path_old = p_root_path.rel_path_to(F->get().first); +bool SceneTreeDock::_update_node_path(Node *p_root_node, NodePath &r_node_path, Map<Node *, NodePath> *p_renames) const { + Node *target_node = p_root_node->get_node_or_null(r_node_path); + ERR_FAIL_NULL_V_MSG(target_node, false, "Found invalid node path '" + String(r_node_path) + "' on node '" + String(scene_root->get_path_to(p_root_node)) + "'"); - // If old path detected, then it needs to be replaced with the new one. - if (r_node_path == rel_path_old) { - NodePath rel_path_new = F->get().second; + // Try to find the target node in modified node paths. + Map<Node *, NodePath>::Element *found_node_path = p_renames->find(target_node); + if (found_node_path) { + Map<Node *, NodePath>::Element *found_root_path = p_renames->find(p_root_node); + NodePath root_path_new = found_root_path ? found_root_path->get() : p_root_node->get_path(); + r_node_path = root_path_new.rel_path_to(found_node_path->get()); - // If not empty, get new relative path. - if (!rel_path_new.is_empty()) { - rel_path_new = root_path_new.rel_path_to(rel_path_new); - } + return true; + } - r_node_path = rel_path_new; - return true; + // Update the path if the base node has changed and has not been deleted. + Map<Node *, NodePath>::Element *found_root_path = p_renames->find(p_root_node); + if (found_root_path) { + NodePath root_path_new = found_root_path->get(); + if (!root_path_new.is_empty()) { + NodePath old_abs_path = NodePath(String(p_root_node->get_path()).plus_file(r_node_path)); + old_abs_path.simplify(); + r_node_path = root_path_new.rel_path_to(old_abs_path); } - // Update the node itself if it has a valid node path and has not been deleted. - if (p_root_path == F->get().first && r_node_path != NodePath() && F->get().second != NodePath()) { - NodePath abs_path = NodePath(String(root_path_new).plus_file(r_node_path)).simplified(); - NodePath rel_path_new = F->get().second.rel_path_to(abs_path); - - r_node_path = rel_path_new; - return true; - } + return true; } return false; } -bool SceneTreeDock::_check_node_path_recursive(const NodePath &p_root_path, Variant &r_variant, List<Pair<NodePath, NodePath>> *p_renames) { +bool SceneTreeDock::_check_node_path_recursive(Node *p_root_node, Variant &r_variant, Map<Node *, NodePath> *p_renames) const { switch (r_variant.get_type()) { case Variant::NODE_PATH: { NodePath node_path = r_variant; - if (_update_node_path(p_root_path, node_path, p_renames)) { + if (!node_path.is_empty() && _update_node_path(p_root_node, node_path, p_renames)) { r_variant = node_path; return true; } @@ -1484,7 +1470,7 @@ bool SceneTreeDock::_check_node_path_recursive(const NodePath &p_root_path, Vari bool updated = false; for (int i = 0; i < a.size(); i++) { Variant value = a[i]; - if (_check_node_path_recursive(p_root_path, value, p_renames)) { + if (_check_node_path_recursive(p_root_node, value, p_renames)) { if (!updated) { a = a.duplicate(); // Need to duplicate for undo-redo to work. updated = true; @@ -1503,7 +1489,7 @@ bool SceneTreeDock::_check_node_path_recursive(const NodePath &p_root_path, Vari bool updated = false; for (int i = 0; i < d.size(); i++) { Variant value = d.get_value_at_index(i); - if (_check_node_path_recursive(p_root_path, value, p_renames)) { + if (_check_node_path_recursive(p_root_node, value, p_renames)) { if (!updated) { d = d.duplicate(); // Need to duplicate for undo-redo to work. updated = true; @@ -1524,7 +1510,7 @@ bool SceneTreeDock::_check_node_path_recursive(const NodePath &p_root_path, Vari return false; } -void SceneTreeDock::perform_node_renames(Node *p_base, List<Pair<NodePath, NodePath>> *p_renames, Map<Ref<Animation>, Set<int>> *r_rem_anims) { +void SceneTreeDock::perform_node_renames(Node *p_base, Map<Node *, NodePath> *p_renames, Map<Ref<Animation>, Set<int>> *r_rem_anims) { Map<Ref<Animation>, Set<int>> rem_anims; if (!r_rem_anims) { r_rem_anims = &rem_anims; @@ -1538,10 +1524,15 @@ void SceneTreeDock::perform_node_renames(Node *p_base, List<Pair<NodePath, NodeP return; } + // No renaming if base node is deleted. + Map<Node *, NodePath>::Element *found_base_path = p_renames->find(p_base); + if (found_base_path && found_base_path->get().is_empty()) { + return; + } + // Renaming node paths used in node properties. List<PropertyInfo> properties; p_base->get_property_list(&properties); - NodePath base_root_path = p_base->get_path(); for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { if (!(E->get().usage & (PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR))) { @@ -1550,7 +1541,7 @@ void SceneTreeDock::perform_node_renames(Node *p_base, List<Pair<NodePath, NodeP String propertyname = E->get().name; Variant old_variant = p_base->get(propertyname); Variant updated_variant = old_variant; - if (_check_node_path_recursive(base_root_path, updated_variant, p_renames)) { + if (_check_node_path_recursive(p_base, updated_variant, p_renames)) { editor_data->get_undo_redo().add_do_property(p_base, propertyname, updated_variant); editor_data->get_undo_redo().add_undo_property(p_base, propertyname, old_variant); p_base->set(propertyname, updated_variant); @@ -1566,19 +1557,9 @@ void SceneTreeDock::perform_node_renames(Node *p_base, List<Pair<NodePath, NodeP Node *root = ap->get_node(ap->get_root()); if (root) { - NodePath root_path = root->get_path(); - NodePath new_root_path = root_path; - - for (List<Pair<NodePath, NodePath>>::Element *E = p_renames->front(); E; E = E->next()) { - if (E->get().first == root_path) { - new_root_path = E->get().second; - break; - } - } - - if (new_root_path != NodePath()) { - //will not be erased - + Map<Node *, NodePath>::Element *found_root_path = p_renames->find(root); + NodePath new_root_path = found_root_path ? found_root_path->get() : root->get_path(); + if (!new_root_path.is_empty()) { // No renaming if root node is deleted. for (List<StringName>::Element *E = anims.front(); E; E = E->next()) { Ref<Animation> anim = ap->get_animation(E->get()); if (!r_rem_anims->has(anim)) { @@ -1602,47 +1583,44 @@ void SceneTreeDock::perform_node_renames(Node *p_base, List<Pair<NodePath, NodeP continue; } - NodePath old_np = n->get_path(); - if (!ran.has(i)) { continue; //channel was removed } - for (List<Pair<NodePath, NodePath>>::Element *F = p_renames->front(); F; F = F->next()) { - if (F->get().first == old_np) { - if (F->get().second == NodePath()) { - //will be erased - - int idx = 0; - Set<int>::Element *EI = ran.front(); - ERR_FAIL_COND(!EI); //bug - while (EI->get() != i) { - idx++; - EI = EI->next(); - ERR_FAIL_COND(!EI); //another bug - } - - editor_data->get_undo_redo().add_do_method(anim.ptr(), "remove_track", idx); - editor_data->get_undo_redo().add_undo_method(anim.ptr(), "add_track", anim->track_get_type(i), idx); - editor_data->get_undo_redo().add_undo_method(anim.ptr(), "track_set_path", idx, track_np); - editor_data->get_undo_redo().add_undo_method(anim.ptr(), "track_set_interpolation_type", idx, anim->track_get_interpolation_type(i)); - for (int j = 0; j < anim->track_get_key_count(i); j++) { - editor_data->get_undo_redo().add_undo_method(anim.ptr(), "track_insert_key", idx, anim->track_get_key_time(i, j), anim->track_get_key_value(i, j), anim->track_get_key_transition(i, j)); - } - - ran.erase(i); //byebye channel - - } else { - //will be renamed - NodePath rel_path = new_root_path.rel_path_to(F->get().second); - - NodePath new_path = NodePath(rel_path.get_names(), track_np.get_subnames(), false); - if (new_path == track_np) { - continue; //bleh - } - editor_data->get_undo_redo().add_do_method(anim.ptr(), "track_set_path", i, new_path); - editor_data->get_undo_redo().add_undo_method(anim.ptr(), "track_set_path", i, track_np); + Map<Node *, NodePath>::Element *found_path = p_renames->find(n); + if (found_path) { + if (found_path->get() == NodePath()) { + //will be erased + + int idx = 0; + Set<int>::Element *EI = ran.front(); + ERR_FAIL_COND(!EI); //bug + while (EI->get() != i) { + idx++; + EI = EI->next(); + ERR_FAIL_COND(!EI); //another bug + } + + editor_data->get_undo_redo().add_do_method(anim.ptr(), "remove_track", idx); + editor_data->get_undo_redo().add_undo_method(anim.ptr(), "add_track", anim->track_get_type(i), idx); + editor_data->get_undo_redo().add_undo_method(anim.ptr(), "track_set_path", idx, track_np); + editor_data->get_undo_redo().add_undo_method(anim.ptr(), "track_set_interpolation_type", idx, anim->track_get_interpolation_type(i)); + for (int j = 0; j < anim->track_get_key_count(i); j++) { + editor_data->get_undo_redo().add_undo_method(anim.ptr(), "track_insert_key", idx, anim->track_get_key_time(i, j), anim->track_get_key_value(i, j), anim->track_get_key_transition(i, j)); } + + ran.erase(i); //byebye channel + + } else { + //will be renamed + NodePath rel_path = new_root_path.rel_path_to(found_path->get()); + + NodePath new_path = NodePath(rel_path.get_names(), track_np.get_subnames(), false); + if (new_path == track_np) { + continue; //bleh + } + editor_data->get_undo_redo().add_do_method(anim.ptr(), "track_set_path", i, new_path); + editor_data->get_undo_redo().add_undo_method(anim.ptr(), "track_set_path", i, track_np); } } } @@ -1657,7 +1635,7 @@ void SceneTreeDock::perform_node_renames(Node *p_base, List<Pair<NodePath, NodeP } void SceneTreeDock::_node_prerenamed(Node *p_node, const String &p_new_name) { - List<Pair<NodePath, NodePath>> path_renames; + Map<Node *, NodePath> path_renames; Vector<StringName> base_path; Node *n = p_node->get_parent(); @@ -1672,10 +1650,8 @@ void SceneTreeDock::_node_prerenamed(Node *p_node, const String &p_new_name) { new_base_path.push_back(p_new_name); - Pair<NodePath, NodePath> npp; - npp.first = NodePath(base_path, true); - npp.second = NodePath(new_base_path, true); - path_renames.push_back(npp); + NodePath new_path(new_base_path, true); + path_renames[p_node] = new_path; for (int i = 0; i < p_node->get_child_count(); i++) { _fill_path_renames(base_path, new_base_path, p_node->get_child(i), &path_renames); @@ -1780,7 +1756,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V editor_data->get_undo_redo().create_action(TTR("Reparent Node")); - List<Pair<NodePath, NodePath>> path_renames; + Map<Node *, NodePath> path_renames; Vector<StringName> former_names; int inc = 0; @@ -1817,21 +1793,24 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V // Name was modified, fix the path renames. if (old_name.casecmp_to(new_name) != 0) { // Fix the to name to have the new name. - NodePath old_new_name = path_renames[ni].second; - NodePath new_path; - - Vector<StringName> unfixed_new_names = old_new_name.get_names(); - Vector<StringName> fixed_new_names; + Map<Node *, NodePath>::Element *found_path = path_renames.find(node); + if (found_path) { + NodePath old_new_name = found_path->get(); - // Get last name and replace with fixed new name. - for (int a = 0; a < (unfixed_new_names.size() - 1); a++) { - fixed_new_names.push_back(unfixed_new_names[a]); - } - fixed_new_names.push_back(new_name); + Vector<StringName> unfixed_new_names = old_new_name.get_names(); + Vector<StringName> fixed_new_names; - NodePath fixed_node_path = NodePath(fixed_new_names, true); + // Get last name and replace with fixed new name. + for (int a = 0; a < (unfixed_new_names.size() - 1); a++) { + fixed_new_names.push_back(unfixed_new_names[a]); + } + fixed_new_names.push_back(new_name); - path_renames[ni].second = fixed_node_path; + NodePath fixed_node_path = NodePath(fixed_new_names, true); + path_renames[node] = fixed_node_path; + } else { + ERR_PRINT("Internal error. Can't find renamed path for node '" + node->get_path() + "'"); + } } editor_data->get_undo_redo().add_do_method(ed, "live_debug_reparent_node", edited_scene->get_path_to(node), edited_scene->get_path_to(new_parent), new_name, p_position_in_parent + inc); @@ -2042,7 +2021,7 @@ void SceneTreeDock::_delete_confirm(bool p_cut) { } else { remove_list.sort_custom<Node::Comparator>(); //sort nodes to keep positions - List<Pair<NodePath, NodePath>> path_renames; + Map<Node *, NodePath> path_renames; //delete from animation for (List<Node *>::Element *E = remove_list.front(); E; E = E->next()) { diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h index fd9299fb2b..4952122cb7 100644 --- a/editor/scene_tree_dock.h +++ b/editor/scene_tree_dock.h @@ -217,7 +217,7 @@ class SceneTreeDock : public VBoxContainer { void _selection_changed(); void _update_script_button(); - void _fill_path_renames(Vector<StringName> base_path, Vector<StringName> new_base_path, Node *p_node, List<Pair<NodePath, NodePath>> *p_renames); + void _fill_path_renames(Vector<StringName> base_path, Vector<StringName> new_base_path, Node *p_node, Map<Node *, NodePath> *p_renames); void _normalize_drop(Node *&to_node, int &to_pos, int p_type); @@ -253,8 +253,8 @@ class SceneTreeDock : public VBoxContainer { static SceneTreeDock *singleton; static void _update_configuration_warning(); - static bool _update_node_path(const NodePath &p_root_path, NodePath &r_node_path, List<Pair<NodePath, NodePath>> *p_renames); - static bool _check_node_path_recursive(const NodePath &p_root_path, Variant &r_variant, List<Pair<NodePath, NodePath>> *p_renames); + bool _update_node_path(Node *p_root_node, NodePath &r_node_path, Map<Node *, NodePath> *p_renames) const; + bool _check_node_path_recursive(Node *p_root_node, Variant &r_variant, Map<Node *, NodePath> *p_renames) const; protected: void _notification(int p_what); @@ -272,8 +272,8 @@ public: void instantiate(const String &p_file); void instantiate_scenes(const Vector<String> &p_files, Node *p_parent = nullptr); void set_selected(Node *p_node, bool p_emit_selected = false); - void fill_path_renames(Node *p_node, Node *p_new_parent, List<Pair<NodePath, NodePath>> *p_renames); - void perform_node_renames(Node *p_base, List<Pair<NodePath, NodePath>> *p_renames, Map<Ref<Animation>, Set<int>> *r_rem_anims = nullptr); + void fill_path_renames(Node *p_node, Node *p_new_parent, Map<Node *, NodePath> *p_renames); + void perform_node_renames(Node *p_base, Map<Node *, NodePath> *p_renames, Map<Ref<Animation>, Set<int>> *r_rem_anims = nullptr); SceneTreeEditor *get_tree_editor() { return scene_tree; } EditorData *get_editor_data() { return editor_data; } diff --git a/main/main.cpp b/main/main.cpp index 1e5445fd93..b7f67e8f6c 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -1080,7 +1080,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph #else const String error_msg = "Error: Couldn't load project data at path \"" + project_path + "\". Is the .pck file missing?\nIf you've renamed the executable, the associated .pck file should also be renamed to match the executable's name (without the extension).\n"; OS::get_singleton()->print("%s", error_msg.ascii().get_data()); - DisplayServer::get_singleton()->alert(error_msg); + OS::get_singleton()->alert(error_msg); goto error; #endif @@ -2015,6 +2015,7 @@ bool Main::start() { // Let's throw an error gently. The code leading to this is pretty brittle so // this might end up triggered by valid usage, in which case we'll have to // fine-tune further. + OS::get_singleton()->alert("Couldn't detect whether to run the editor, the project manager or a specific project. Aborting."); ERR_FAIL_V_MSG(false, "Couldn't detect whether to run the editor, the project manager or a specific project. Aborting."); } #endif @@ -2044,9 +2045,8 @@ bool Main::start() { if (obj) { memdelete(obj); } - ERR_FAIL_V_MSG(false, - vformat("Can't load the script \"%s\" as it doesn't inherit from SceneTree or MainLoop.", - script)); + OS::get_singleton()->alert(vformat("Can't load the script \"%s\" as it doesn't inherit from SceneTree or MainLoop.", script)); + ERR_FAIL_V_MSG(false, vformat("Can't load the script \"%s\" as it doesn't inherit from SceneTree or MainLoop.", script)); } script_loop->set_initialize_script(script_res); @@ -2065,7 +2065,7 @@ bool Main::start() { if (obj) { memdelete(obj); } - DisplayServer::get_singleton()->alert("Error: Invalid MainLoop script base type: " + script_base); + OS::get_singleton()->alert("Error: Invalid MainLoop script base type: " + script_base); ERR_FAIL_V_MSG(false, vformat("The global class %s does not inherit from SceneTree or MainLoop.", main_loop_type)); } script_loop->set_initialize_script(script_res); @@ -2079,7 +2079,7 @@ bool Main::start() { if (!main_loop) { if (!ClassDB::class_exists(main_loop_type)) { - DisplayServer::get_singleton()->alert("Error: MainLoop type doesn't exist: " + main_loop_type); + OS::get_singleton()->alert("Error: MainLoop type doesn't exist: " + main_loop_type); return false; } else { Object *ml = ClassDB::instantiate(main_loop_type); diff --git a/modules/gdnative/include/net/godot_webrtc.h b/modules/gdnative/include/net/godot_webrtc.h index 62b7e3c2c5..52006e56ec 100644 --- a/modules/gdnative/include/net/godot_webrtc.h +++ b/modules/gdnative/include/net/godot_webrtc.h @@ -37,8 +37,8 @@ extern "C" { #endif -#define GODOT_NET_WEBRTC_API_MAJOR 3 -#define GODOT_NET_WEBRTC_API_MINOR 2 +#define GODOT_NET_WEBRTC_API_MAJOR 4 +#define GODOT_NET_WEBRTC_API_MINOR 0 /* Library Interface (used to set default GDNative WebRTC implementation */ typedef struct { diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 839aa6b3c6..e68deb070d 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -241,16 +241,18 @@ </methods> <constants> <constant name="PI" value="3.14159265358979"> - Constant that represents how many times the diameter of a circle fits around its perimeter. This is equivalent to [code]TAU / 2[/code]. + Constant that represents how many times the diameter of a circle fits around its perimeter. This is equivalent to [code]TAU / 2[/code], or 180 degrees in rotations. </constant> <constant name="TAU" value="6.28318530717959"> - The circle constant, the circumference of the unit circle in radians. + The circle constant, the circumference of the unit circle in radians. This is equivalent to [code]PI * 2[/code], or 360 degrees in rotations. </constant> <constant name="INF" value="inf"> - Positive infinity. For negative infinity, use -INF. + Positive floating-point infinity. This is the result of floating-point division when the divisor is [code]0.0[/code]. For negative infinity, use [code]-INF[/code]. Dividing by [code]-0.0[/code] will result in negative infinity if the numerator is positive, so dividing by [code]0.0[/code] is not the same as dividing by [code]-0.0[/code] (despite [code]0.0 == -0.0[/code] returning [code]true[/code]). + [b]Note:[/b] Numeric infinity is only a concept with floating-point numbers, and has no equivalent for integers. Dividing an integer number by [code]0[/code] will not result in [constant INF] and will result in a run-time error instead. </constant> <constant name="NAN" value="nan"> - "Not a Number", an invalid value. [code]NaN[/code] has special properties, including that it is not equal to itself. It is output by some invalid operations, such as dividing zero by zero. + "Not a Number", an invalid floating-point value. [constant NAN] has special properties, including that it is not equal to itself ([code]NAN == NAN[/code] returns [code]false[/code]). It is output by some invalid operations, such as dividing floating-point [code]0.0[/code] by [code]0.0[/code]. + [b]Note:[/b] "Not a Number" is only a concept with floating-point numbers, and has no equivalent for integers. Dividing an integer [code]0[/code] by [code]0[/code] will not result in [constant NAN] and will result in a run-time error instead. </constant> </constants> </class> diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 71a9b3667f..30d023606f 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -2796,7 +2796,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { mat = mat3d; } - import_mesh->add_surface(primitive, array, morphs, Dictionary(), mat); + import_mesh->add_surface(primitive, array, morphs, Dictionary(), mat, mat.is_valid() ? mat->get_name() : String()); } Vector<float> blend_weights; diff --git a/modules/visual_script/visual_script_func_nodes.cpp b/modules/visual_script/visual_script_func_nodes.cpp index 7b5ca56dcc..a6c52a0f8c 100644 --- a/modules/visual_script/visual_script_func_nodes.cpp +++ b/modules/visual_script/visual_script_func_nodes.cpp @@ -2268,7 +2268,7 @@ int VisualScriptEmitSignal::get_input_value_port_count() const { } int VisualScriptEmitSignal::get_output_value_port_count() const { - return 1; + return 0; } String VisualScriptEmitSignal::get_output_sequence_port_text(int p_port) const { @@ -2311,16 +2311,6 @@ StringName VisualScriptEmitSignal::get_signal() const { return name; } -void VisualScriptEmitSignal::_adjust_input_index(PropertyInfo &pinfo) const { - if (index != StringName()) { - Variant v; - Callable::CallError ce; - Variant::construct(pinfo.type, v, nullptr, 0, ce); - Variant i = v.get(index); - pinfo.type = i.get_type(); - } -} - void VisualScriptEmitSignal::_validate_property(PropertyInfo &property) const { if (property.name == "signal") { property.hint = PROPERTY_HINT_ENUM; diff --git a/modules/visual_script/visual_script_func_nodes.h b/modules/visual_script/visual_script_func_nodes.h index 37a707d108..cca08455f9 100644 --- a/modules/visual_script/visual_script_func_nodes.h +++ b/modules/visual_script/visual_script_func_nodes.h @@ -328,9 +328,6 @@ class VisualScriptEmitSignal : public VisualScriptNode { private: StringName name; - StringName index; - - void _adjust_input_index(PropertyInfo &pinfo) const; protected: virtual void _validate_property(PropertyInfo &property) const override; diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index 1fcc3d4a5c..af8829324b 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -334,13 +334,6 @@ bool DisplayServerAndroid::can_any_window_draw() const { return true; } -void DisplayServerAndroid::alert(const String &p_alert, const String &p_title) { - GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java(); - ERR_FAIL_COND(!godot_java); - - godot_java->alert(p_alert, p_title); -} - void DisplayServerAndroid::process_events() { Input::get_singleton()->flush_accumulated_events(); } @@ -361,7 +354,7 @@ Vector<String> DisplayServerAndroid::get_rendering_drivers_func() { DisplayServer *DisplayServerAndroid::create_func(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { DisplayServer *ds = memnew(DisplayServerAndroid(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_resolution, r_error)); if (r_error != OK) { - ds->alert("Your video card driver does not support any of the supported Vulkan versions.", "Unable to initialize Video driver"); + OS::get_singleton()->alert("Your video card driver does not support any of the supported Vulkan versions.", "Unable to initialize Video driver"); } return ds; } diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h index 46745f8030..9b9f5e99f6 100644 --- a/platform/android/display_server_android.h +++ b/platform/android/display_server_android.h @@ -204,8 +204,6 @@ public: virtual void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID) override; virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) const override; - virtual void alert(const String &p_alert, const String &p_title) override; - virtual void process_events() override; void process_accelerometer(const Vector3 &p_accelerometer); diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 222976d948..792a390e36 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -71,6 +71,13 @@ public: virtual ~AndroidLogger() {} }; +void OS_Android::alert(const String &p_alert, const String &p_title) { + GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java(); + ERR_FAIL_COND(!godot_java); + + godot_java->alert(p_alert, p_title); +} + void OS_Android::initialize_core() { OS_Unix::initialize_core(); diff --git a/platform/android/os_android.h b/platform/android/os_android.h index 1e89e9211d..38f0f3edc7 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -86,6 +86,8 @@ public: virtual bool request_permissions() override; virtual Vector<String> get_granted_permissions() const override; + virtual void alert(const String &p_alert, const String &p_title) override; + virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false) override; virtual String get_name() const override; diff --git a/platform/iphone/display_server_iphone.h b/platform/iphone/display_server_iphone.h index 6f64130b23..8ba7a69a4f 100644 --- a/platform/iphone/display_server_iphone.h +++ b/platform/iphone/display_server_iphone.h @@ -119,8 +119,6 @@ public: virtual bool has_feature(Feature p_feature) const override; virtual String get_name() const override; - virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; - virtual int get_screen_count() const override; virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; diff --git a/platform/iphone/display_server_iphone.mm b/platform/iphone/display_server_iphone.mm index bd95e2c703..b4f0a32027 100644 --- a/platform/iphone/display_server_iphone.mm +++ b/platform/iphone/display_server_iphone.mm @@ -320,12 +320,6 @@ String DisplayServerIPhone::get_name() const { return "iPhone"; } -void DisplayServerIPhone::alert(const String &p_alert, const String &p_title) { - const CharString utf8_alert = p_alert.utf8(); - const CharString utf8_title = p_title.utf8(); - iOS::alert(utf8_alert.get_data(), utf8_title.get_data()); -} - int DisplayServerIPhone::get_screen_count() const { return 1; } diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h index f4ff909adf..8abc3b7ed5 100644 --- a/platform/iphone/os_iphone.h +++ b/platform/iphone/os_iphone.h @@ -92,13 +92,12 @@ public: void start(); + virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; + virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false) override; virtual Error close_dynamic_library(void *p_library_handle) override; virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false) override; - virtual void alert(const String &p_alert, - const String &p_title = "ALERT!") override; - virtual String get_name() const override; virtual String get_model_name() const override; diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm index 1f08901082..b69996ccf0 100644 --- a/platform/iphone/os_iphone.mm +++ b/platform/iphone/os_iphone.mm @@ -114,6 +114,12 @@ OSIPhone::OSIPhone(String p_data_dir) { OSIPhone::~OSIPhone() {} +void OSIPhone::alert(const String &p_alert, const String &p_title) { + const CharString utf8_alert = p_alert.utf8(); + const CharString utf8_title = p_title.utf8(); + iOS::alert(utf8_alert.get_data(), utf8_title.get_data()); +} + void OSIPhone::initialize_core() { OS_Unix::initialize_core(); @@ -221,12 +227,6 @@ Error OSIPhone::get_dynamic_library_symbol_handle(void *p_library_handle, const return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional); } -void OSIPhone::alert(const String &p_alert, const String &p_title) { - const CharString utf8_alert = p_alert.utf8(); - const CharString utf8_title = p_title.utf8(); - iOS::alert(utf8_alert.get_data(), utf8_title.get_data()); -} - String OSIPhone::get_name() const { return "iOS"; }; diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp index 5e2b089c7f..8df81bb8cc 100644 --- a/platform/javascript/display_server_javascript.cpp +++ b/platform/javascript/display_server_javascript.cpp @@ -659,10 +659,6 @@ void DisplayServerJavaScript::send_window_event_callback(int p_notification) { } } -void DisplayServerJavaScript::alert(const String &p_alert, const String &p_title) { - godot_js_display_alert(p_alert.utf8().get_data()); -} - void DisplayServerJavaScript::set_icon(const Ref<Image> &p_icon) { ERR_FAIL_COND(p_icon.is_null()); Ref<Image> icon = p_icon; diff --git a/platform/javascript/display_server_javascript.h b/platform/javascript/display_server_javascript.h index f8acf30854..bf5e229c9a 100644 --- a/platform/javascript/display_server_javascript.h +++ b/platform/javascript/display_server_javascript.h @@ -109,7 +109,6 @@ public: bool check_size_force_redraw(); // from DisplayServer - virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; virtual bool has_feature(Feature p_feature) const override; virtual String get_name() const override; diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index 260bfad7a5..549c1d47ec 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -47,6 +47,10 @@ #include "godot_js.h" +void OS_JavaScript::alert(const String &p_alert, const String &p_title) { + godot_js_display_alert(p_alert.utf8().get_data()); +} + // Lifecycle void OS_JavaScript::initialize() { OS_Unix::initialize_core(); diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h index 81bb9c5f3d..efac2dbca7 100644 --- a/platform/javascript/os_javascript.h +++ b/platform/javascript/os_javascript.h @@ -89,6 +89,9 @@ public: String get_user_data_dir() const override; bool is_userfs_persistent() const override; + + void alert(const String &p_alert, const String &p_title = "ALERT!") override; + Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) override; void resume_audio(); diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index 8f0742041c..430f8cc4c4 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -136,70 +136,6 @@ String DisplayServerX11::get_name() const { return "X11"; } -void DisplayServerX11::alert(const String &p_alert, const String &p_title) { - const char *message_programs[] = { "zenity", "kdialog", "Xdialog", "xmessage" }; - - String path = OS::get_singleton()->get_environment("PATH"); - Vector<String> path_elems = path.split(":", false); - String program; - - for (int i = 0; i < path_elems.size(); i++) { - for (uint64_t k = 0; k < sizeof(message_programs) / sizeof(char *); k++) { - String tested_path = path_elems[i].plus_file(message_programs[k]); - - if (FileAccess::exists(tested_path)) { - program = tested_path; - break; - } - } - - if (program.length()) { - break; - } - } - - List<String> args; - - if (program.ends_with("zenity")) { - args.push_back("--error"); - args.push_back("--width"); - args.push_back("500"); - args.push_back("--title"); - args.push_back(p_title); - args.push_back("--text"); - args.push_back(p_alert); - } - - if (program.ends_with("kdialog")) { - args.push_back("--error"); - args.push_back(p_alert); - args.push_back("--title"); - args.push_back(p_title); - } - - if (program.ends_with("Xdialog")) { - args.push_back("--title"); - args.push_back(p_title); - args.push_back("--msgbox"); - args.push_back(p_alert); - args.push_back("0"); - args.push_back("0"); - } - - if (program.ends_with("xmessage")) { - args.push_back("-center"); - args.push_back("-title"); - args.push_back(p_title); - args.push_back(p_alert); - } - - if (program.length()) { - OS::get_singleton()->execute(program, args); - } else { - print_line(p_alert); - } -} - void DisplayServerX11::_update_real_mouse_position(const WindowData &wd) { Window root_return, child_return; int root_x, root_y, win_x, win_y; @@ -871,7 +807,9 @@ DisplayServer::WindowID DisplayServerX11::create_sub_window(WindowMode p_mode, V void DisplayServerX11::show_window(WindowID p_id) { _THREAD_SAFE_METHOD_ - WindowData &wd = windows[p_id]; + const WindowData &wd = windows[p_id]; + + DEBUG_LOG_X11("show_window: %lu (%u) \n", wd.x11_window, p_id); XMapWindow(x11_display, wd.x11_window); } @@ -1095,6 +1033,8 @@ void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent WindowID prev_parent = wd_window.transient_parent; ERR_FAIL_COND(prev_parent == p_parent); + DEBUG_LOG_X11("window_set_transient: %lu (%u), prev_parent=%u, parent=%u\n", wd_window.x11_window, p_window, prev_parent, p_parent); + ERR_FAIL_COND_MSG(wd_window.on_top, "Windows with the 'on top' can't become transient."); if (p_parent == INVALID_WINDOW_ID) { //remove transient @@ -1109,10 +1049,10 @@ void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent XSetTransientForHint(x11_display, wd_window.x11_window, None); - // Set focus to parent sub window to avoid losing all focus with nested menus. + // Set focus to parent sub window to avoid losing all focus when closing a nested sub-menu. // RevertToPointerRoot is used to make sure we don't lose all focus in case // a subwindow and its parent are both destroyed. - if (wd_window.menu_type && !wd_window.no_focus) { + if (wd_window.menu_type && !wd_window.no_focus && wd_window.focused) { if (!wd_parent.no_focus) { XSetInputFocus(x11_display, wd_parent.x11_window, RevertToPointerRoot, CurrentTime); } @@ -3673,8 +3613,8 @@ Vector<String> DisplayServerX11::get_rendering_drivers_func() { DisplayServer *DisplayServerX11::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { DisplayServer *ds = memnew(DisplayServerX11(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_resolution, r_error)); if (r_error != OK) { - ds->alert("Your video card driver does not support any of the supported Vulkan versions.\n" - "Please update your drivers or if you have a very old or integrated GPU upgrade it.", + OS::get_singleton()->alert("Your video card driver does not support any of the supported Vulkan versions.\n" + "Please update your drivers or if you have a very old or integrated GPU upgrade it.", "Unable to initialize Video driver"); } return ds; @@ -3972,8 +3912,8 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode } if (!_refresh_device_info()) { - alert("Your system does not support XInput 2.\n" - "Please upgrade your distribution.", + OS::get_singleton()->alert("Your system does not support XInput 2.\n" + "Please upgrade your distribution.", "Unable to initialize XInput"); r_error = ERR_UNAVAILABLE; return; diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h index 29c15ca2ba..052c6d6b7b 100644 --- a/platform/linuxbsd/display_server_x11.h +++ b/platform/linuxbsd/display_server_x11.h @@ -280,8 +280,6 @@ public: virtual bool has_feature(Feature p_feature) const override; virtual String get_name() const override; - virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; - virtual void mouse_set_mode(MouseMode p_mode) override; virtual MouseMode mouse_get_mode() const override; diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 8cd6ec43c6..ba63a009fe 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -51,6 +51,70 @@ #include <sys/types.h> #include <unistd.h> +void OS_LinuxBSD::alert(const String &p_alert, const String &p_title) { + const char *message_programs[] = { "zenity", "kdialog", "Xdialog", "xmessage" }; + + String path = get_environment("PATH"); + Vector<String> path_elems = path.split(":", false); + String program; + + for (int i = 0; i < path_elems.size(); i++) { + for (uint64_t k = 0; k < sizeof(message_programs) / sizeof(char *); k++) { + String tested_path = path_elems[i].plus_file(message_programs[k]); + + if (FileAccess::exists(tested_path)) { + program = tested_path; + break; + } + } + + if (program.length()) { + break; + } + } + + List<String> args; + + if (program.ends_with("zenity")) { + args.push_back("--error"); + args.push_back("--width"); + args.push_back("500"); + args.push_back("--title"); + args.push_back(p_title); + args.push_back("--text"); + args.push_back(p_alert); + } + + if (program.ends_with("kdialog")) { + args.push_back("--error"); + args.push_back(p_alert); + args.push_back("--title"); + args.push_back(p_title); + } + + if (program.ends_with("Xdialog")) { + args.push_back("--title"); + args.push_back(p_title); + args.push_back("--msgbox"); + args.push_back(p_alert); + args.push_back("0"); + args.push_back("0"); + } + + if (program.ends_with("xmessage")) { + args.push_back("-center"); + args.push_back("-title"); + args.push_back(p_title); + args.push_back(p_alert); + } + + if (program.length()) { + execute(program, args); + } else { + print_line(p_alert); + } +} + void OS_LinuxBSD::initialize() { crash_handler.initialize(); diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h index b6cf93c551..1e06587322 100644 --- a/platform/linuxbsd/os_linuxbsd.h +++ b/platform/linuxbsd/os_linuxbsd.h @@ -90,6 +90,8 @@ public: virtual String get_unique_id() const override; + virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; + virtual bool _check_internal_feature_support(const String &p_feature) override; void run(); diff --git a/platform/osx/detect.py b/platform/osx/detect.py index 317e79d0ea..2393f2ea10 100644 --- a/platform/osx/detect.py +++ b/platform/osx/detect.py @@ -24,6 +24,7 @@ def get_opts(): return [ ("osxcross_sdk", "OSXCross SDK version", "darwin16"), ("MACOS_SDK_PATH", "Path to the macOS SDK", ""), + ("VULKAN_SDK_PATH", "Path to the Vulkan SDK", ""), BoolVariable( "use_static_mvk", "Link MoltenVK statically as Level-0 driver (better portability) or use Vulkan ICD loader (enables" @@ -190,7 +191,7 @@ def configure(env): env.Append(CPPDEFINES=["VULKAN_ENABLED"]) env.Append(LINKFLAGS=["-framework", "Metal", "-framework", "QuartzCore", "-framework", "IOSurface"]) if env["use_static_mvk"]: - env.Append(LINKFLAGS=["-framework", "MoltenVK"]) + env.Append(LINKFLAGS=["-L$VULKAN_SDK_PATH/MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/", "-lMoltenVK"]) env["builtin_vulkan"] = False elif not env["builtin_vulkan"]: env.Append(LIBS=["vulkan"]) diff --git a/platform/osx/display_server_osx.h b/platform/osx/display_server_osx.h index c7b9e411b8..6b1b777224 100644 --- a/platform/osx/display_server_osx.h +++ b/platform/osx/display_server_osx.h @@ -60,6 +60,10 @@ class DisplayServerOSX : public DisplayServer { _THREAD_SAFE_CLASS_ public: + void _send_event(NSEvent *p_event); + NSMenu *_get_dock_menu() const; + void _menu_callback(id p_sender); + #if defined(OPENGL_ENABLED) ContextGL_OSX *context_gles2; #endif @@ -163,7 +167,6 @@ public: String rendering_driver; - id delegate; id autoreleasePool; CGEventSourceRef eventSource; @@ -207,7 +210,6 @@ public: virtual void global_menu_remove_item(const String &p_menu_root, int p_idx) override; virtual void global_menu_clear(const String &p_menu_root) override; - virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; virtual Error dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) override; virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) override; diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm index dec6da42fe..73aa013701 100644 --- a/platform/osx/display_server_osx.mm +++ b/platform/osx/display_server_osx.mm @@ -105,46 +105,6 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { } /*************************************************************************/ -/* GodotApplication */ -/*************************************************************************/ - -@interface GodotApplication : NSApplication -@end - -@implementation GodotApplication - -- (void)sendEvent:(NSEvent *)event { - // special case handling of command-period, which is traditionally a special - // shortcut in macOS and doesn't arrive at our regular keyDown handler. - if ([event type] == NSEventTypeKeyDown) { - if (([event modifierFlags] & NSEventModifierFlagCommand) && [event keyCode] == 0x2f) { - Ref<InputEventKey> k; - k.instantiate(); - - _get_key_modifier_state([event modifierFlags], k); - k->set_window_id(DisplayServerOSX::INVALID_WINDOW_ID); - k->set_pressed(true); - k->set_keycode(KEY_PERIOD); - k->set_physical_keycode(KEY_PERIOD); - k->set_echo([event isARepeat]); - - Input::get_singleton()->accumulate_input_event(k); - } - } - - // From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost - // This works around an AppKit bug, where key up events while holding - // down the command key don't get sent to the key window. - if ([event type] == NSEventTypeKeyUp && ([event modifierFlags] & NSEventModifierFlagCommand)) { - [[self keyWindow] sendEvent:event]; - } else { - [super sendEvent:event]; - } -} - -@end - -/*************************************************************************/ /* GlobalMenuItem */ /*************************************************************************/ @@ -161,121 +121,6 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { @end /*************************************************************************/ -/* GodotApplicationDelegate */ -/*************************************************************************/ - -@interface GodotApplicationDelegate : NSObject -- (void)forceUnbundledWindowActivationHackStep1; -- (void)forceUnbundledWindowActivationHackStep2; -- (void)forceUnbundledWindowActivationHackStep3; -@end - -@implementation GodotApplicationDelegate - -- (void)forceUnbundledWindowActivationHackStep1 { - // Step1: Switch focus to macOS Dock. - // Required to perform step 2, TransformProcessType will fail if app is already the in focus. - for (NSRunningApplication *app in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.dock"]) { - [app activateWithOptions:NSApplicationActivateIgnoringOtherApps]; - break; - } - [self performSelector:@selector(forceUnbundledWindowActivationHackStep2) withObject:nil afterDelay:0.02]; -} - -- (void)forceUnbundledWindowActivationHackStep2 { - // Step 2: Register app as foreground process. - ProcessSerialNumber psn = { 0, kCurrentProcess }; - (void)TransformProcessType(&psn, kProcessTransformToForegroundApplication); - [self performSelector:@selector(forceUnbundledWindowActivationHackStep3) withObject:nil afterDelay:0.02]; -} - -- (void)forceUnbundledWindowActivationHackStep3 { - // Step 3: Switch focus back to app window. - [[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps]; -} - -- (void)applicationDidFinishLaunching:(NSNotification *)notice { - NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; - if (nsappname == nil) { - // If executable is not a bundled, macOS WindowServer won't register and activate app window correctly (menu and title bar are grayed out and input ignored). - [self performSelector:@selector(forceUnbundledWindowActivationHackStep1) withObject:nil afterDelay:0.02]; - } -} - -- (void)applicationDidResignActive:(NSNotification *)notification { - if (OS_OSX::get_singleton()->get_main_loop()) { - OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT); - } -} - -- (void)applicationDidBecomeActive:(NSNotification *)notification { - if (OS_OSX::get_singleton()->get_main_loop()) { - OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN); - } -} - -- (void)globalMenuCallback:(id)sender { - if (![sender representedObject]) { - return; - } - - GlobalMenuItem *value = [sender representedObject]; - - if (value) { - if (value->checkable) { - if ([sender state] == NSControlStateValueOff) { - [sender setState:NSControlStateValueOn]; - } else { - [sender setState:NSControlStateValueOff]; - } - } - - if (value->callback != Callable()) { - Variant tag = value->meta; - Variant *tagp = &tag; - Variant ret; - Callable::CallError ce; - value->callback.call((const Variant **)&tagp, 1, ret, ce); - } - } -} - -- (NSMenu *)applicationDockMenu:(NSApplication *)sender { - return DS_OSX->dock_menu; -} - -- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename { - // Note: may be called called before main loop init! - char *utfs = strdup([filename UTF8String]); - ((OS_OSX *)(OS_OSX::get_singleton()))->open_with_filename.parse_utf8(utfs); - free(utfs); - -#ifdef TOOLS_ENABLED - // Open new instance - if (OS_OSX::get_singleton()->get_main_loop()) { - List<String> args; - args.push_back(((OS_OSX *)(OS_OSX::get_singleton()))->open_with_filename); - String exec = OS::get_singleton()->get_executable_path(); - OS::get_singleton()->create_process(exec, args); - } -#endif - return YES; -} - -- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { - DS_OSX->_send_window_event(DS_OSX->windows[DisplayServerOSX::MAIN_WINDOW_ID], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST); - return NSTerminateCancel; -} - -- (void)showAbout:(id)sender { - if (OS_OSX::get_singleton()->get_main_loop()) { - OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_ABOUT); - } -} - -@end - -/*************************************************************************/ /* GodotWindowDelegate */ /*************************************************************************/ @@ -1983,26 +1828,6 @@ void DisplayServerOSX::global_menu_clear(const String &p_menu_root) { } } -void DisplayServerOSX::alert(const String &p_alert, const String &p_title) { - _THREAD_SAFE_METHOD_ - - NSAlert *window = [[NSAlert alloc] init]; - NSString *ns_title = [NSString stringWithUTF8String:p_title.utf8().get_data()]; - NSString *ns_alert = [NSString stringWithUTF8String:p_alert.utf8().get_data()]; - - [window addButtonWithTitle:@"OK"]; - [window setMessageText:ns_title]; - [window setInformativeText:ns_alert]; - [window setAlertStyle:NSAlertStyleWarning]; - - id key_window = [[NSApplication sharedApplication] keyWindow]; - [window runModal]; - [window release]; - if (key_window) { - [key_window makeKeyAndOrderFront:nil]; - } -} - Error DisplayServerOSX::dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) { _THREAD_SAFE_METHOD_ @@ -3375,6 +3200,56 @@ void DisplayServerOSX::_release_pressed_events() { } } +NSMenu *DisplayServerOSX::_get_dock_menu() const { + return dock_menu; +} + +void DisplayServerOSX::_menu_callback(id p_sender) { + if (![p_sender representedObject]) { + return; + } + + GlobalMenuItem *value = [p_sender representedObject]; + + if (value) { + if (value->checkable) { + if ([p_sender state] == NSControlStateValueOff) { + [p_sender setState:NSControlStateValueOn]; + } else { + [p_sender setState:NSControlStateValueOff]; + } + } + + if (value->callback != Callable()) { + Variant tag = value->meta; + Variant *tagp = &tag; + Variant ret; + Callable::CallError ce; + value->callback.call((const Variant **)&tagp, 1, ret, ce); + } + } +} + +void DisplayServerOSX::_send_event(NSEvent *p_event) { + // special case handling of command-period, which is traditionally a special + // shortcut in macOS and doesn't arrive at our regular keyDown handler. + if ([p_event type] == NSEventTypeKeyDown) { + if (([p_event modifierFlags] & NSEventModifierFlagCommand) && [p_event keyCode] == 0x2f) { + Ref<InputEventKey> k; + k.instantiate(); + + _get_key_modifier_state([p_event modifierFlags], k); + k->set_window_id(DisplayServerOSX::INVALID_WINDOW_ID); + k->set_pressed(true); + k->set_keycode(KEY_PERIOD); + k->set_physical_keycode(KEY_PERIOD); + k->set_echo([p_event isARepeat]); + + Input::get_singleton()->accumulate_input_event(k); + } + } +} + void DisplayServerOSX::_process_key_events() { Ref<InputEventKey> k; for (int i = 0; i < key_event_pos; i++) { @@ -3615,7 +3490,7 @@ ObjectID DisplayServerOSX::window_get_attached_instance_id(WindowID p_window) co DisplayServer *DisplayServerOSX::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { DisplayServer *ds = memnew(DisplayServerOSX(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_resolution, r_error)); if (r_error != OK) { - ds->alert("Your video card driver does not support any of the supported Metal versions.", "Unable to initialize Video driver"); + OS::get_singleton()->alert("Your video card driver does not support any of the supported Metal versions.", "Unable to initialize Video driver"); } return ds; } @@ -3785,12 +3660,6 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode CGEventSourceSetLocalEventsSuppressionInterval(eventSource, 0.0); - // Implicitly create shared NSApplication instance - [GodotApplication sharedApplication]; - - // In case we are unbundled, make us a proper UI application - [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; - keyboard_layout_dirty = true; displays_arrangement_dirty = true; displays_scale_dirty = true; @@ -3804,9 +3673,6 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode // Register to be notified on displays arrangement changes CGDisplayRegisterReconfigurationCallback(displays_arrangement_changed, nullptr); - // Menu bar setup must go between sharedApplication above and - // finishLaunching below, in order to properly emulate the behavior - // of NSApplicationMain NSMenuItem *menu_item; NSString *title; @@ -3846,32 +3712,10 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode title = [NSString stringWithFormat:NSLocalizedString(@"Quit %@", nil), nsappname]; [apple_menu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; - // Setup menu bar - NSMenu *main_menu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; + // Add items to the menu bar + NSMenu *main_menu = [NSApp mainMenu]; menu_item = [main_menu addItemWithTitle:@"" action:nil keyEquivalent:@""]; [main_menu setSubmenu:apple_menu forItem:menu_item]; - [NSApp setMainMenu:main_menu]; - - [NSApp finishLaunching]; - - delegate = [[GodotApplicationDelegate alloc] init]; - ERR_FAIL_COND(!delegate); - [NSApp setDelegate:delegate]; - - //process application:openFile: event - while (true) { - NSEvent *event = [NSApp - nextEventMatchingMask:NSEventMaskAny - untilDate:[NSDate distantPast] - inMode:NSDefaultRunLoopMode - dequeue:YES]; - - if (event == nil) { - break; - } - - [NSApp sendEvent:event]; - } //!!!!!!!!!!!!!!!!!!!!!!!!!! //TODO - do Vulkan and GLES2 support checks, driver selection and fallback @@ -3924,8 +3768,6 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode RendererCompositorRD::make_current(); } #endif - - [NSApp activateIgnoringOtherApps:YES]; } DisplayServerOSX::~DisplayServerOSX() { diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h index d57940775d..37d30add78 100644 --- a/platform/osx/os_osx.h +++ b/platform/osx/os_osx.h @@ -72,6 +72,8 @@ protected: public: virtual String get_name() const override; + virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; + virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false) override; virtual MainLoop *get_main_loop() const override; diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index b65d84d900..b8361c0b3e 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -41,6 +41,137 @@ #include <mach-o/dyld.h> #include <os/log.h> +#define DS_OSX ((DisplayServerOSX *)(DisplayServerOSX::get_singleton())) + +/*************************************************************************/ +/* GodotApplication */ +/*************************************************************************/ + +@interface GodotApplication : NSApplication +@end + +@implementation GodotApplication + +- (void)sendEvent:(NSEvent *)event { + if (DS_OSX) { + DS_OSX->_send_event(event); + } + + // From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost + // This works around an AppKit bug, where key up events while holding + // down the command key don't get sent to the key window. + if ([event type] == NSEventTypeKeyUp && ([event modifierFlags] & NSEventModifierFlagCommand)) { + [[self keyWindow] sendEvent:event]; + } else { + [super sendEvent:event]; + } +} + +@end + +/*************************************************************************/ +/* GodotApplicationDelegate */ +/*************************************************************************/ + +@interface GodotApplicationDelegate : NSObject +- (void)forceUnbundledWindowActivationHackStep1; +- (void)forceUnbundledWindowActivationHackStep2; +- (void)forceUnbundledWindowActivationHackStep3; +@end + +@implementation GodotApplicationDelegate + +- (void)forceUnbundledWindowActivationHackStep1 { + // Step1: Switch focus to macOS Dock. + // Required to perform step 2, TransformProcessType will fail if app is already the in focus. + for (NSRunningApplication *app in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.dock"]) { + [app activateWithOptions:NSApplicationActivateIgnoringOtherApps]; + break; + } + [self performSelector:@selector(forceUnbundledWindowActivationHackStep2) + withObject:nil + afterDelay:0.02]; +} + +- (void)forceUnbundledWindowActivationHackStep2 { + // Step 2: Register app as foreground process. + ProcessSerialNumber psn = { 0, kCurrentProcess }; + (void)TransformProcessType(&psn, kProcessTransformToForegroundApplication); + [self performSelector:@selector(forceUnbundledWindowActivationHackStep3) withObject:nil afterDelay:0.02]; +} + +- (void)forceUnbundledWindowActivationHackStep3 { + // Step 3: Switch focus back to app window. + [[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps]; +} + +- (void)applicationDidFinishLaunching:(NSNotification *)notice { + NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; + if (nsappname == nil) { + // If executable is not a bundled, macOS WindowServer won't register and activate app window correctly (menu and title bar are grayed out and input ignored). + [self performSelector:@selector(forceUnbundledWindowActivationHackStep1) withObject:nil afterDelay:0.02]; + } +} + +- (void)applicationDidResignActive:(NSNotification *)notification { + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT); + } +} + +- (void)applicationDidBecomeActive:(NSNotification *)notification { + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN); + } +} + +- (void)globalMenuCallback:(id)sender { + if (DS_OSX) { + return DS_OSX->_menu_callback(sender); + } +} + +- (NSMenu *)applicationDockMenu:(NSApplication *)sender { + if (DS_OSX) { + return DS_OSX->_get_dock_menu(); + } else { + return nullptr; + } +} + +- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename { + // Note: may be called called before main loop init! + char *utfs = strdup([filename UTF8String]); + ((OS_OSX *)OS_OSX::get_singleton())->open_with_filename.parse_utf8(utfs); + free(utfs); + +#ifdef TOOLS_ENABLED + // Open new instance + if (OS_OSX::get_singleton()->get_main_loop()) { + List<String> args; + args.push_back(((OS_OSX *)OS_OSX::get_singleton())->open_with_filename); + String exec = OS_OSX::get_singleton()->get_executable_path(); + OS_OSX::get_singleton()->create_process(exec, args); + } +#endif + return YES; +} + +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { + if (DS_OSX) { + DS_OSX->_send_window_event(DS_OSX->windows[DisplayServerOSX::MAIN_WINDOW_ID], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST); + } + return NSTerminateCancel; +} + +- (void)showAbout:(id)sender { + if (OS_OSX::get_singleton()->get_main_loop()) { + OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_ABOUT); + } +} + +@end + /*************************************************************************/ /* OSXTerminalLogger */ /*************************************************************************/ @@ -119,6 +250,24 @@ String OS_OSX::get_unique_id() const { return serial_number; } +void OS_OSX::alert(const String &p_alert, const String &p_title) { + NSAlert *window = [[NSAlert alloc] init]; + NSString *ns_title = [NSString stringWithUTF8String:p_title.utf8().get_data()]; + NSString *ns_alert = [NSString stringWithUTF8String:p_alert.utf8().get_data()]; + + [window addButtonWithTitle:@"OK"]; + [window setMessageText:ns_title]; + [window setInformativeText:ns_alert]; + [window setAlertStyle:NSAlertStyleWarning]; + + id key_window = [[NSApplication sharedApplication] keyWindow]; + [window runModal]; + [window release]; + if (key_window) { + [key_window makeKeyAndOrderFront:nil]; + } +} + void OS_OSX::initialize_core() { OS_Unix::initialize_core(); @@ -372,6 +521,41 @@ OS_OSX::OS_OSX() { #endif DisplayServerOSX::register_osx_driver(); + + // Implicitly create shared NSApplication instance + [GodotApplication sharedApplication]; + + // In case we are unbundled, make us a proper UI application + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + + // Menu bar setup must go between sharedApplication above and + // finishLaunching below, in order to properly emulate the behavior + // of NSApplicationMain + + NSMenu *main_menu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; + [NSApp setMainMenu:main_menu]; + [NSApp finishLaunching]; + + id delegate = [[GodotApplicationDelegate alloc] init]; + ERR_FAIL_COND(!delegate); + [NSApp setDelegate:delegate]; + + //process application:openFile: event + while (true) { + NSEvent *event = [NSApp + nextEventMatchingMask:NSEventMaskAny + untilDate:[NSDate distantPast] + inMode:NSDefaultRunLoopMode + dequeue:YES]; + + if (event == nil) { + break; + } + + [NSApp sendEvent:event]; + } + + [NSApp activateIgnoringOtherApps:YES]; } bool OS_OSX::_check_internal_feature_support(const String &p_feature) { diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 4f64809abc..e78b23c509 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -79,10 +79,6 @@ String DisplayServerWindows::get_name() const { return "Windows"; } -void DisplayServerWindows::alert(const String &p_alert, const String &p_title) { - MessageBoxW(nullptr, (LPCWSTR)(p_alert.utf16().get_data()), (LPCWSTR)(p_title.utf16().get_data()), MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL); -} - void DisplayServerWindows::_set_mouse_mode_impl(MouseMode p_mode) { if (p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_CONFINED || p_mode == MOUSE_MODE_CONFINED_HIDDEN) { // Mouse is grabbed (captured or confined). @@ -3358,8 +3354,8 @@ Vector<String> DisplayServerWindows::get_rendering_drivers_func() { DisplayServer *DisplayServerWindows::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { DisplayServer *ds = memnew(DisplayServerWindows(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_resolution, r_error)); if (r_error != OK) { - ds->alert("Your video card driver does not support any of the supported Vulkan versions.\n" - "Please update your drivers or if you have a very old or integrated GPU upgrade it.", + OS::get_singleton()->alert("Your video card driver does not support any of the supported Vulkan versions.\n" + "Please update your drivers or if you have a very old or integrated GPU upgrade it.", "Unable to initialize Video driver"); } return ds; diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index aa1d42d6ed..06014fbabe 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -442,8 +442,6 @@ public: virtual bool has_feature(Feature p_feature) const override; virtual String get_name() const override; - virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; - virtual void mouse_set_mode(MouseMode p_mode) override; virtual MouseMode mouse_get_mode() const override; diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 56d673afc3..a06362b9b0 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -165,6 +165,10 @@ BOOL WINAPI HandlerRoutine(_In_ DWORD dwCtrlType) { } } +void OS_Windows::alert(const String &p_alert, const String &p_title) { + MessageBoxW(nullptr, (LPCWSTR)(p_alert.utf16().get_data()), (LPCWSTR)(p_title.utf16().get_data()), MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL); +} + void OS_Windows::initialize_debugging() { SetConsoleCtrlHandler(HandlerRoutine, TRUE); } diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 8f9ef254f1..ea0c263b78 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -108,6 +108,8 @@ protected: Map<ProcessID, ProcessInfo> *process_map; public: + virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; + virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false) override; virtual Error close_dynamic_library(void *p_library_handle) override; virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false) override; diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 6d1e27bf1b..bf2f8f8947 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -58,7 +58,7 @@ void TileMapPattern::remove_cell(const Vector2i &p_coords, bool p_update_size) { } int TileMapPattern::get_cell_source_id(const Vector2i &p_coords) const { - ERR_FAIL_COND_V(!pattern.has(p_coords), -1); + ERR_FAIL_COND_V(!pattern.has(p_coords), TileSet::INVALID_SOURCE); return pattern[p_coords].source_id; } @@ -113,7 +113,7 @@ void TileMapPattern::clear() { }; void TileMapPattern::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMapPattern::set_cell, DEFVAL(-1), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetSource::INVALID_TILE_ALTERNATIVE)); + ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMapPattern::set_cell, DEFVAL(TileSet::INVALID_SOURCE), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetSource::INVALID_TILE_ALTERNATIVE)); ClassDB::bind_method(D_METHOD("has_cell", "coords"), &TileMapPattern::has_cell); ClassDB::bind_method(D_METHOD("remove_cell", "coords"), &TileMapPattern::remove_cell); ClassDB::bind_method(D_METHOD("get_cell_source_id", "coords"), &TileMapPattern::get_cell_source_id); @@ -513,15 +513,15 @@ void TileMap::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i Vector2i atlas_coords = p_atlas_coords; int alternative_tile = p_alternative_tile; - if ((source_id == -1 || atlas_coords == TileSetSource::INVALID_ATLAS_COORDS || alternative_tile == TileSetSource::INVALID_TILE_ALTERNATIVE) && - (source_id != -1 || atlas_coords != TileSetSource::INVALID_ATLAS_COORDS || alternative_tile != TileSetSource::INVALID_TILE_ALTERNATIVE)) { + if ((source_id == TileSet::INVALID_SOURCE || atlas_coords == TileSetSource::INVALID_ATLAS_COORDS || alternative_tile == TileSetSource::INVALID_TILE_ALTERNATIVE) && + (source_id != TileSet::INVALID_SOURCE || atlas_coords != TileSetSource::INVALID_ATLAS_COORDS || alternative_tile != TileSetSource::INVALID_TILE_ALTERNATIVE)) { WARN_PRINT("Setting a cell a cell as empty requires both source_id, atlas_coord and alternative_tile to be set to their respective \"invalid\" values. Values were thus changes accordingly."); - source_id = -1; + source_id = TileSet::INVALID_SOURCE; atlas_coords = TileSetSource::INVALID_ATLAS_COORDS; alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; } - if (!E && source_id == -1) { + if (!E && source_id == TileSet::INVALID_SOURCE) { return; // Nothing to do, the tile is already empty. } @@ -530,7 +530,7 @@ void TileMap::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i Map<Vector2i, TileMapQuadrant>::Element *Q = quadrant_map.find(qk); - if (source_id == -1) { + if (source_id == TileSet::INVALID_SOURCE) { // Erase existing cell in the tile map. tile_map.erase(pk); @@ -579,18 +579,23 @@ void TileMap::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i } } -int TileMap::get_cell_source_id(const Vector2i &p_coords) const { +int TileMap::get_cell_source_id(const Vector2i &p_coords, bool p_use_proxies) const { // Get a cell source id from position const Map<Vector2i, TileMapCell>::Element *E = tile_map.find(p_coords); if (!E) { - return -1; + return TileSet::INVALID_SOURCE; + } + + if (p_use_proxies && tile_set.is_valid()) { + Array proxyed = tile_set->map_tile_proxy(E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + return proxyed[0]; } return E->get().source_id; } -Vector2i TileMap::get_cell_atlas_coords(const Vector2i &p_coords) const { +Vector2i TileMap::get_cell_atlas_coords(const Vector2i &p_coords, bool p_use_proxies) const { // Get a cell source id from position const Map<Vector2i, TileMapCell>::Element *E = tile_map.find(p_coords); @@ -598,10 +603,15 @@ Vector2i TileMap::get_cell_atlas_coords(const Vector2i &p_coords) const { return TileSetSource::INVALID_ATLAS_COORDS; } + if (p_use_proxies && tile_set.is_valid()) { + Array proxyed = tile_set->map_tile_proxy(E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + return proxyed[1]; + } + return E->get().get_atlas_coords(); } -int TileMap::get_cell_alternative_tile(const Vector2i &p_coords) const { +int TileMap::get_cell_alternative_tile(const Vector2i &p_coords, bool p_use_proxies) const { // Get a cell source id from position const Map<Vector2i, TileMapCell>::Element *E = tile_map.find(p_coords); @@ -609,6 +619,11 @@ int TileMap::get_cell_alternative_tile(const Vector2i &p_coords) const { return TileSetSource::INVALID_TILE_ALTERNATIVE; } + if (p_use_proxies && tile_set.is_valid()) { + Array proxyed = tile_set->map_tile_proxy(E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + return proxyed[2]; + } + return E->get().alternative_tile; } @@ -697,11 +712,18 @@ void TileMap::set_pattern(Vector2i p_position, const TileMapPattern *p_pattern) } } -TileMapCell TileMap::get_cell(const Vector2i &p_coords) const { +TileMapCell TileMap::get_cell(const Vector2i &p_coords, bool p_use_proxies) const { if (!tile_map.has(p_coords)) { return TileMapCell(); } else { - return tile_map.find(p_coords)->get(); + TileMapCell c = tile_map.find(p_coords)->get(); + if (p_use_proxies && tile_set.is_valid()) { + Array proxyed = tile_set->map_tile_proxy(c.source_id, c.get_atlas_coords(), c.alternative_tile); + c.source_id = proxyed[0]; + c.set_atlas_coords(proxyed[1]); + c.alternative_tile = proxyed[2]; + } + return c; } } @@ -720,7 +742,7 @@ void TileMap::fix_invalid_tiles() { } } for (Set<Vector2i>::Element *E = coords.front(); E; E = E->next()) { - set_cell(E->get(), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); + set_cell(E->get(), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); } } @@ -799,25 +821,28 @@ void TileMap::_set_tile_data(const Vector<int> &p_data) { SWAP(local[9], local[10]); } #endif + // Extracts position in TileMap. int16_t x = decode_uint16(&local[0]); int16_t y = decode_uint16(&local[2]); if (format == FORMAT_3) { uint16_t source_id = decode_uint16(&local[4]); uint16_t atlas_coords_x = decode_uint16(&local[6]); - uint16_t atlas_coords_y = decode_uint32(&local[8]); + uint16_t atlas_coords_y = decode_uint16(&local[8]); uint16_t alternative_tile = decode_uint16(&local[10]); set_cell(Vector2i(x, y), source_id, Vector2i(atlas_coords_x, atlas_coords_y), alternative_tile); } else { #ifndef DISABLE_DEPRECATED - uint32_t v = decode_uint32(&local[4]); - v &= (1 << 29) - 1; + // Previous decated format. - // We generate an alternative tile number out of the the flags - // An option should create the alternative in the tileset for compatibility + uint32_t v = decode_uint32(&local[4]); + // Extract the transform flags that used to be in the tilemap. bool flip_h = v & (1 << 29); bool flip_v = v & (1 << 30); bool transpose = v & (1 << 31); + v &= (1 << 29) - 1; + + // Extract autotile/atlas coords. int16_t coord_x = 0; int16_t coord_y = 0; if (format == FORMAT_2) { @@ -825,13 +850,17 @@ void TileMap::_set_tile_data(const Vector<int> &p_data) { coord_y = decode_uint16(&local[10]); } - int compatibility_alternative_tile = ((int)flip_h) + ((int)flip_v << 1) + ((int)transpose << 2); - if (tile_set.is_valid()) { - v = tile_set->compatibility_get_source_for_tile_id(v); + Array a = tile_set->compatibility_tilemap_map(v, Vector2i(coord_x, coord_y), flip_h, flip_v, transpose); + if (a.size() == 3) { + set_cell(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(Vector2i(x, y), v, Vector2i(coord_x, coord_y), compatibility_alternative_tile); } - - set_cell(Vector2i(x, y), v, Vector2i(coord_x, coord_y), compatibility_alternative_tile); #endif } } @@ -1727,10 +1756,10 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_navigation_visibility_mode", "show_navigation"), &TileMap::set_navigation_visibility_mode); ClassDB::bind_method(D_METHOD("get_navigation_visibility_mode"), &TileMap::get_navigation_visibility_mode); - ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMap::set_cell, DEFVAL(-1), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetSource::INVALID_TILE_ALTERNATIVE)); - ClassDB::bind_method(D_METHOD("get_cell_source_id", "coords"), &TileMap::get_cell_source_id); - ClassDB::bind_method(D_METHOD("get_cell_atlas_coords", "coords"), &TileMap::get_cell_atlas_coords); - ClassDB::bind_method(D_METHOD("get_cell_alternative_tile", "coords"), &TileMap::get_cell_alternative_tile); + ClassDB::bind_method(D_METHOD("set_cell", "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", "coords", "use_proxies"), &TileMap::get_cell_source_id); + ClassDB::bind_method(D_METHOD("get_cell_atlas_coords", "coords", "use_proxies"), &TileMap::get_cell_atlas_coords); + ClassDB::bind_method(D_METHOD("get_cell_alternative_tile", "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); diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 3001e6b471..9e35e73ad8 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -265,16 +265,16 @@ public: VisibilityMode get_navigation_visibility_mode(); void set_cell(const Vector2i &p_coords, int p_source_id = -1, const Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE); - int get_cell_source_id(const Vector2i &p_coords) const; - Vector2i get_cell_atlas_coords(const Vector2i &p_coords) const; - int get_cell_alternative_tile(const Vector2i &p_coords) const; + int get_cell_source_id(const Vector2i &p_coords, bool p_use_proxies = false) const; + Vector2i get_cell_atlas_coords(const Vector2i &p_coords, bool p_use_proxies = false) const; + int get_cell_alternative_tile(const Vector2i &p_coords, bool p_use_proxies = false) const; TileMapPattern *get_pattern(TypedArray<Vector2i> p_coords_array); Vector2i map_pattern(Vector2i p_position_in_tilemap, Vector2i p_coords_in_pattern, const TileMapPattern *p_pattern); void set_pattern(Vector2i p_position, const TileMapPattern *p_pattern); // Not exposed to users - TileMapCell get_cell(const Vector2i &p_coords) const; + TileMapCell get_cell(const Vector2i &p_coords, bool p_use_proxies = false) const; Map<Vector2i, TileMapQuadrant> &get_quadrant_map(); int get_effective_quadrant_size() const; diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp index 9e0bc9f3a1..60f8ad8f36 100644 --- a/scene/3d/cpu_particles_3d.cpp +++ b/scene/3d/cpu_particles_3d.cpp @@ -401,6 +401,22 @@ void CPUParticles3D::set_emission_colors(const Vector<Color> &p_colors) { emission_colors = p_colors; } +void CPUParticles3D::set_emission_ring_axis(Vector3 p_axis) { + emission_ring_axis = p_axis; +} + +void CPUParticles3D::set_emission_ring_height(float p_height) { + emission_ring_height = p_height; +} + +void CPUParticles3D::set_emission_ring_radius(float p_radius) { + emission_ring_radius = p_radius; +} + +void CPUParticles3D::set_emission_ring_inner_radius(float p_radius) { + emission_ring_inner_radius = p_radius; +} + float CPUParticles3D::get_emission_sphere_radius() const { return emission_sphere_radius; } @@ -421,6 +437,22 @@ Vector<Color> CPUParticles3D::get_emission_colors() const { return emission_colors; } +Vector3 CPUParticles3D::get_emission_ring_axis() const { + return emission_ring_axis; +} + +float CPUParticles3D::get_emission_ring_height() const { + return emission_ring_height; +} + +float CPUParticles3D::get_emission_ring_radius() const { + return emission_ring_radius; +} + +float CPUParticles3D::get_emission_ring_inner_radius() const { + return emission_ring_inner_radius; +} + CPUParticles3D::EmissionShape CPUParticles3D::get_emission_shape() const { return emission_shape; } @@ -442,7 +474,7 @@ void CPUParticles3D::_validate_property(PropertyInfo &property) const { property.usage = PROPERTY_USAGE_NONE; } - if ((property.name == "emission_point_texture" || property.name == "emission_color_texture") && (emission_shape < EMISSION_SHAPE_POINTS)) { + if ((property.name == "emission_point_texture" || property.name == "emission_color_texture" || property.name == "emission_points") && (emission_shape != EMISSION_SHAPE_POINTS && (emission_shape != EMISSION_SHAPE_DIRECTED_POINTS))) { property.usage = PROPERTY_USAGE_NONE; } @@ -450,6 +482,10 @@ void CPUParticles3D::_validate_property(PropertyInfo &property) const { property.usage = PROPERTY_USAGE_NONE; } + if (property.name.begins_with("emission_ring_") && emission_shape != EMISSION_SHAPE_RING) { + property.usage = PROPERTY_USAGE_NONE; + } + if (property.name.begins_with("orbit_") && !particle_flags[PARTICLE_FLAG_DISABLE_Z]) { property.usage = PROPERTY_USAGE_NONE; } @@ -746,6 +782,21 @@ void CPUParticles3D::_particles_process(float p_delta) { p.base_color = emission_colors.get(random_idx); } } break; + case EMISSION_SHAPE_RING: { + float ring_random_angle = Math::randf() * 2.0 * Math_PI; + float ring_random_radius = Math::randf() * (emission_ring_radius - emission_ring_inner_radius) + emission_ring_inner_radius; + Vector3 axis = emission_ring_axis.normalized(); + Vector3 ortho_axis = Vector3(); + if (axis == Vector3(1.0, 0.0, 0.0)) { + ortho_axis = Vector3(0.0, 1.0, 0.0).cross(axis); + } else { + ortho_axis = Vector3(1.0, 0.0, 0.0).cross(axis); + } + ortho_axis = ortho_axis.normalized(); + ortho_axis.rotate(axis, ring_random_angle); + ortho_axis = ortho_axis.normalized(); + p.transform.origin = ortho_axis * ring_random_radius + (Math::randf() * emission_ring_height - emission_ring_height / 2.0) * axis; + } break; case EMISSION_SHAPE_MAX: { // Max value for validity check. break; } @@ -1336,18 +1387,34 @@ void CPUParticles3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_emission_colors", "array"), &CPUParticles3D::set_emission_colors); ClassDB::bind_method(D_METHOD("get_emission_colors"), &CPUParticles3D::get_emission_colors); + ClassDB::bind_method(D_METHOD("set_emission_ring_axis", "axis"), &CPUParticles3D::set_emission_ring_axis); + ClassDB::bind_method(D_METHOD("get_emission_ring_axis"), &CPUParticles3D::get_emission_ring_axis); + + ClassDB::bind_method(D_METHOD("set_emission_ring_height", "height"), &CPUParticles3D::set_emission_ring_height); + ClassDB::bind_method(D_METHOD("get_emission_ring_height"), &CPUParticles3D::get_emission_ring_height); + + ClassDB::bind_method(D_METHOD("set_emission_ring_radius", "radius"), &CPUParticles3D::set_emission_ring_radius); + ClassDB::bind_method(D_METHOD("get_emission_ring_radius"), &CPUParticles3D::get_emission_ring_radius); + + ClassDB::bind_method(D_METHOD("set_emission_ring_inner_radius", "inner_radius"), &CPUParticles3D::set_emission_ring_inner_radius); + ClassDB::bind_method(D_METHOD("get_emission_ring_inner_radius"), &CPUParticles3D::get_emission_ring_inner_radius); + ClassDB::bind_method(D_METHOD("get_gravity"), &CPUParticles3D::get_gravity); ClassDB::bind_method(D_METHOD("set_gravity", "accel_vec"), &CPUParticles3D::set_gravity); ClassDB::bind_method(D_METHOD("convert_from_particles", "particles"), &CPUParticles3D::convert_from_particles); ADD_GROUP("Emission Shape", "emission_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_shape", PROPERTY_HINT_ENUM, "Point,Sphere,Box,Points,Directed Points", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_emission_shape", "get_emission_shape"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_shape", PROPERTY_HINT_ENUM, "Point,Sphere,Box,Points,Directed Points,Ring", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_emission_shape", "get_emission_shape"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_sphere_radius", PROPERTY_HINT_RANGE, "0.01,128,0.01"), "set_emission_sphere_radius", "get_emission_sphere_radius"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_box_extents"), "set_emission_box_extents", "get_emission_box_extents"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "emission_points"), "set_emission_points", "get_emission_points"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "emission_normals"), "set_emission_normals", "get_emission_normals"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "emission_colors"), "set_emission_colors", "get_emission_colors"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_ring_axis"), "set_emission_ring_axis", "get_emission_ring_axis"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height"), "set_emission_ring_height", "get_emission_ring_height"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius"), "set_emission_ring_radius", "get_emission_ring_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius"); ADD_GROUP("Particle Flags", "particle_flag_"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_align_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_rotate_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ROTATE_Y); @@ -1433,6 +1500,7 @@ void CPUParticles3D::_bind_methods() { BIND_ENUM_CONSTANT(EMISSION_SHAPE_BOX); BIND_ENUM_CONSTANT(EMISSION_SHAPE_POINTS); BIND_ENUM_CONSTANT(EMISSION_SHAPE_DIRECTED_POINTS); + BIND_ENUM_CONSTANT(EMISSION_SHAPE_RING); BIND_ENUM_CONSTANT(EMISSION_SHAPE_MAX); } @@ -1461,6 +1529,10 @@ CPUParticles3D::CPUParticles3D() { set_emission_shape(EMISSION_SHAPE_POINT); set_emission_sphere_radius(1); set_emission_box_extents(Vector3(1, 1, 1)); + set_emission_ring_axis(Vector3(0, 0, 1.0)); + set_emission_ring_height(1); + set_emission_ring_radius(1); + set_emission_ring_inner_radius(0); set_gravity(Vector3(0, -9.8, 0)); diff --git a/scene/3d/cpu_particles_3d.h b/scene/3d/cpu_particles_3d.h index b35e659757..07d345ba2c 100644 --- a/scene/3d/cpu_particles_3d.h +++ b/scene/3d/cpu_particles_3d.h @@ -76,6 +76,7 @@ public: EMISSION_SHAPE_BOX, EMISSION_SHAPE_POINTS, EMISSION_SHAPE_DIRECTED_POINTS, + EMISSION_SHAPE_RING, EMISSION_SHAPE_MAX }; @@ -171,6 +172,10 @@ private: Vector<Vector3> emission_normals; Vector<Color> emission_colors; int emission_point_count = 0; + Vector3 emission_ring_axis; + float emission_ring_height; + float emission_ring_radius; + float emission_ring_inner_radius; Vector3 gravity = Vector3(0, -9.8, 0); @@ -268,6 +273,10 @@ public: void set_emission_normals(const Vector<Vector3> &p_normals); void set_emission_colors(const Vector<Color> &p_colors); void set_emission_point_count(int p_count); + void set_emission_ring_axis(Vector3 p_axis); + void set_emission_ring_height(float p_height); + void set_emission_ring_radius(float p_radius); + void set_emission_ring_inner_radius(float p_radius); EmissionShape get_emission_shape() const; float get_emission_sphere_radius() const; @@ -276,6 +285,10 @@ public: Vector<Vector3> get_emission_normals() const; Vector<Color> get_emission_colors() const; int get_emission_point_count() const; + Vector3 get_emission_ring_axis() const; + float get_emission_ring_height() const; + float get_emission_ring_radius() const; + float get_emission_ring_inner_radius() const; void set_gravity(const Vector3 &p_gravity); Vector3 get_gravity() const; diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index 1e9baa77fc..73034dee5a 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -55,6 +55,11 @@ void MenuButton::_unhandled_key_input(Ref<InputEvent> p_event) { } } +void MenuButton::_popup_visibility_changed(bool p_visible) { + set_pressed(p_visible); + set_process_internal(p_visible); +} + void MenuButton::pressed() { Size2 size = get_size(); @@ -94,10 +99,26 @@ bool MenuButton::is_switch_on_hover() { } void MenuButton::_notification(int p_what) { - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - if (!is_visible_in_tree()) { - popup->hide(); - } + switch (p_what) { + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible_in_tree()) { + popup->hide(); + } + } break; + case NOTIFICATION_INTERNAL_PROCESS: { + if (switch_on_hover) { + Window *window = Object::cast_to<Window>(get_viewport()); + if (window) { + Vector2i mouse_pos = DisplayServer::get_singleton()->mouse_get_position() - window->get_position(); + MenuButton *menu_btn_other = Object::cast_to<MenuButton>(window->gui_find_control(mouse_pos)); + if (menu_btn_other && menu_btn_other != this && menu_btn_other->is_switch_on_hover() && !menu_btn_other->is_disabled() && + (get_parent()->is_ancestor_of(menu_btn_other) || menu_btn_other->get_parent()->is_ancestor_of(popup))) { + popup->hide(); + menu_btn_other->pressed(); + } + } + } + } break; } } @@ -130,8 +151,8 @@ MenuButton::MenuButton() { popup = memnew(PopupMenu); popup->hide(); add_child(popup); - popup->connect("about_to_popup", callable_mp((BaseButton *)this, &BaseButton::set_pressed), varray(true)); // For when switching from another MenuButton. - popup->connect("popup_hide", callable_mp((BaseButton *)this, &BaseButton::set_pressed), varray(false)); + popup->connect("about_to_popup", callable_mp(this, &MenuButton::_popup_visibility_changed), varray(true)); + popup->connect("popup_hide", callable_mp(this, &MenuButton::_popup_visibility_changed), varray(false)); } MenuButton::~MenuButton() { diff --git a/scene/gui/menu_button.h b/scene/gui/menu_button.h index fd9ae6021e..301769b008 100644 --- a/scene/gui/menu_button.h +++ b/scene/gui/menu_button.h @@ -47,6 +47,8 @@ class MenuButton : public Button { void _gui_input(Ref<InputEvent> p_event) override; + void _popup_visibility_changed(bool p_visible); + protected: void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index 5e1c8cec37..f7e7e1cd60 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -72,6 +72,7 @@ void Popup::_notification(int p_what) { } else { _deinitialize_visible_parents(); emit_signal(SNAME("popup_hide")); + popped_up = false; } } break; diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 74718395d3..aedc5d0155 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -140,7 +140,6 @@ class PopupMenu : public Popup { void _close_pressed(); protected: - friend class MenuButton; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 2fe5d7aa78..644ce92018 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -82,6 +82,14 @@ bool SceneTreeTimer::is_process_always() { return process_always; } +void SceneTreeTimer::set_ignore_time_scale(bool p_ignore) { + ignore_time_scale = p_ignore; +} + +bool SceneTreeTimer::is_ignore_time_scale() { + return ignore_time_scale; +} + void SceneTreeTimer::release_connections() { List<Connection> connections; get_all_signal_connections(&connections); @@ -466,8 +474,13 @@ bool SceneTree::process(float p_time) { E = N; continue; } + float time_left = E->get()->get_time_left(); - time_left -= p_time; + if (E->get()->is_ignore_time_scale()) { + time_left -= Engine::get_singleton()->get_process_step(); + } else { + time_left -= p_time; + } E->get()->set_time_left(time_left); if (time_left < 0) { diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index 0be0e185a5..c3d59663d6 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -54,6 +54,7 @@ class SceneTreeTimer : public RefCounted { float time_left = 0.0; bool process_always = true; + bool ignore_time_scale = false; protected: static void _bind_methods(); @@ -65,6 +66,9 @@ public: void set_process_always(bool p_process_always); bool is_process_always(); + void set_ignore_time_scale(bool p_ignore); + bool is_ignore_time_scale(); + void release_connections(); SceneTreeTimer(); diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 5a0a27520b..34a871b993 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -502,17 +502,6 @@ void Viewport::_notification(int p_what) { RS::get_singleton()->viewport_set_active(viewport, false); RenderingServer::get_singleton()->viewport_set_parent_viewport(viewport, RID()); - - } break; - case NOTIFICATION_INTERNAL_PROCESS: { - if (gui.tooltip_timer >= 0) { - gui.tooltip_timer -= get_process_delta_time(); - if (gui.tooltip_timer < 0) { - _gui_show_tooltip(); - set_process_internal(false); - } - } - } break; case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { if (get_tree()->is_debugging_collisions_hint() && contact_2d_debug.is_valid()) { @@ -1489,8 +1478,10 @@ void Viewport::_gui_sort_roots() { void Viewport::_gui_cancel_tooltip() { gui.tooltip_control = nullptr; - gui.tooltip_timer = -1; - set_process_internal(false); + if (gui.tooltip_timer.is_valid()) { + gui.tooltip_timer->release_connections(); + gui.tooltip_timer = Ref<SceneTreeTimer>(); + } if (gui.tooltip_popup) { gui.tooltip_popup->queue_delete(); gui.tooltip_popup = nullptr; @@ -1710,7 +1701,7 @@ void Viewport::_gui_call_notification(Control *p_control, int p_what) { //_unblock(); } -Control *Viewport::_gui_find_control(const Point2 &p_global) { +Control *Viewport::gui_find_control(const Point2 &p_global) { //aca va subwindows _gui_sort_roots(); @@ -1850,7 +1841,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { parent_xform=data.parent_canvas_item->get_global_transform(); */ - gui.mouse_focus = _gui_find_control(pos); + gui.mouse_focus = gui_find_control(pos); gui.last_mouse_focus = gui.mouse_focus; if (!gui.mouse_focus) { @@ -1991,7 +1982,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (gui.mouse_focus) { over = gui.mouse_focus; } else { - over = _gui_find_control(mpos); + over = gui_find_control(mpos); } if (gui.mouse_focus_mask == 0 && over != gui.mouse_over) { @@ -2074,7 +2065,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (gui.mouse_focus) { over = gui.mouse_focus; } else { - over = _gui_find_control(mpos); + over = gui_find_control(mpos); } if (over != gui.mouse_over) { @@ -2139,10 +2130,15 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } if (can_tooltip && !is_tooltip_shown) { + if (gui.tooltip_timer.is_valid()) { + gui.tooltip_timer->release_connections(); + gui.tooltip_timer = Ref<SceneTreeTimer>(); + } gui.tooltip_control = over; gui.tooltip_pos = over->get_screen_transform().xform(pos); - gui.tooltip_timer = gui.tooltip_delay; - set_process_internal(true); + gui.tooltip_timer = get_tree()->create_timer(gui.tooltip_delay); + gui.tooltip_timer->set_ignore_time_scale(true); + gui.tooltip_timer->connect("timeout", callable_mp(this, &Viewport::_gui_show_tooltip)); } } @@ -2262,7 +2258,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Transform2D ai = (viewport_under->get_final_transform().affine_inverse() * viewport_under->_get_input_pre_xform()); viewport_pos = ai.xform(viewport_pos); //find control under at pos - gui.drag_mouse_over = viewport_under->_gui_find_control(viewport_pos); + gui.drag_mouse_over = viewport_under->gui_find_control(viewport_pos); if (gui.drag_mouse_over) { Transform2D localizer = gui.drag_mouse_over->get_global_transform_with_canvas().affine_inverse(); gui.drag_mouse_over_pos = localizer.xform(viewport_pos); @@ -2290,7 +2286,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (touch_event.is_valid()) { Size2 pos = touch_event->get_position(); if (touch_event->is_pressed()) { - Control *over = _gui_find_control(pos); + Control *over = gui_find_control(pos); if (over) { if (over->can_process()) { touch_event = touch_event->xformed_by(Transform2D()); //make a copy @@ -2325,7 +2321,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Size2 pos = gesture_event->get_position(); - Control *over = _gui_find_control(pos); + Control *over = gui_find_control(pos); if (over) { if (over->can_process()) { gesture_event = gesture_event->xformed_by(Transform2D()); //make a copy @@ -2346,7 +2342,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (drag_event.is_valid()) { Control *over = gui.mouse_focus; if (!over) { - over = _gui_find_control(drag_event->get_position()); + over = gui_find_control(drag_event->get_position()); } if (over) { if (over->can_process()) { diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 32b6ba84b5..b5c49a8a97 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -50,6 +50,7 @@ class Label; class Timer; class Viewport; class CollisionObject3D; +class SceneTreeTimer; class ViewportTexture : public Texture2D { GDCLASS(ViewportTexture, Texture2D); @@ -373,7 +374,7 @@ private: bool drag_attempted = false; Variant drag_data; ObjectID drag_preview_id; - float tooltip_timer = -1.0; + Ref<SceneTreeTimer> tooltip_timer; float tooltip_delay = 0.0; Transform2D focus_inv_xform; bool roots_order_dirty = false; @@ -404,7 +405,6 @@ private: void _gui_call_notification(Control *p_control, int p_what); void _gui_sort_roots(); - Control *_gui_find_control(const Point2 &p_global); Control *_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_global, const Transform2D &p_xform, Transform2D &r_inv_xform); void _gui_input_event(Ref<InputEvent> p_event); @@ -629,6 +629,8 @@ public: bool gui_is_dragging() const; + Control *gui_find_control(const Point2 &p_global); + void set_sdf_oversize(SDFOversize p_sdf_oversize); SDFOversize get_sdf_oversize() const; diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 2d83f59bb5..439fe649a1 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -1085,6 +1085,10 @@ void unregister_scene_types() { BaseMaterial3D::finish_shaders(); #endif // _3D_DISABLED + PhysicalSkyMaterial::cleanup_shader(); + PanoramaSkyMaterial::cleanup_shader(); + ProceduralSkyMaterial::cleanup_shader(); + ParticlesMaterial::finish_shaders(); CanvasItemMaterial::finish_shaders(); ColorPicker::finish_shaders(); diff --git a/scene/resources/particles_material.cpp b/scene/resources/particles_material.cpp index 00bea312b3..7da9cb96ee 100644 --- a/scene/resources/particles_material.cpp +++ b/scene/resources/particles_material.cpp @@ -90,6 +90,10 @@ void ParticlesMaterial::init_shaders() { shader_names->emission_texture_points = "emission_texture_points"; shader_names->emission_texture_normal = "emission_texture_normal"; shader_names->emission_texture_color = "emission_texture_color"; + shader_names->emission_ring_axis = "emission_ring_axis"; + shader_names->emission_ring_height = "emission_ring_height"; + shader_names->emission_ring_radius = "emission_ring_radius"; + shader_names->emission_ring_inner_radius = "emission_ring_inner_radius"; shader_names->gravity = "gravity"; @@ -194,6 +198,12 @@ void ParticlesMaterial::_update_shader() { code += "uniform sampler2D emission_texture_color : hint_white;\n"; } } break; + case EMISSION_SHAPE_RING: { + code += "uniform vec3 " + shader_names->emission_ring_axis + ";\n"; + code += "uniform float " + shader_names->emission_ring_height + ";\n"; + code += "uniform float " + shader_names->emission_ring_radius + ";\n"; + code += "uniform float " + shader_names->emission_ring_inner_radius + ";\n"; + } break; case EMISSION_SHAPE_MAX: { // Max value for validity check. break; } @@ -396,6 +406,28 @@ void ParticlesMaterial::_update_shader() { } } } break; + case EMISSION_SHAPE_RING: { + code += " float ring_spawn_angle = rand_from_seed(alt_seed) * 2.0 * pi;\n"; + code += " float ring_random_radius = rand_from_seed(alt_seed) * (emission_ring_radius - emission_ring_inner_radius) + emission_ring_inner_radius;\n"; + code += " vec3 axis = normalize(emission_ring_axis);\n"; + code += " vec3 ortho_axis = vec3(0.0);\n"; + code += " if (axis == vec3(1.0, 0.0, 0.0)) {\n"; + code += " ortho_axis = cross(axis, vec3(0.0, 1.0, 0.0));\n"; + code += " } else {\n"; + code += " ortho_axis = cross(axis, vec3(1.0, 0.0, 0.0));\n"; + code += " }\n"; + code += " ortho_axis = normalize(ortho_axis);\n"; + code += " float s = sin(ring_spawn_angle);\n"; + code += " float c = cos(ring_spawn_angle);\n"; + code += " float oc = 1.0 - c;\n"; + code += " ortho_axis = mat3(\n"; + code += " vec3(c + axis.x * axis.x * oc, axis.x * axis.y * oc - axis.z * s, axis.x * axis.z *oc + axis.y * s),\n"; + code += " vec3(axis.x * axis.y * oc + s * axis.z, c + axis.y * axis.y * oc, axis.y * axis.z * oc - axis.x * s),\n"; + code += " vec3(axis.z * axis.x * oc - axis.y * s, axis.z * axis.y * oc + axis.x * s, c + axis.z * axis.z * oc)\n"; + code += " ) * ortho_axis;\n"; + code += " ortho_axis = normalize(ortho_axis);\n"; + code += " TRANSFORM[3].xyz = ortho_axis * ring_random_radius + (rand_from_seed(alt_seed) * emission_ring_height - emission_ring_height / 2.0) * axis;\n"; + } break; case EMISSION_SHAPE_MAX: { // Max value for validity check. break; } @@ -990,6 +1022,26 @@ void ParticlesMaterial::set_emission_point_count(int p_count) { RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_texture_point_count, p_count); } +void ParticlesMaterial::set_emission_ring_axis(Vector3 p_axis) { + emission_ring_axis = p_axis; + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_ring_axis, p_axis); +} + +void ParticlesMaterial::set_emission_ring_height(float p_height) { + emission_ring_height = p_height; + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_ring_height, p_height); +} + +void ParticlesMaterial::set_emission_ring_radius(float p_radius) { + emission_ring_radius = p_radius; + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_ring_radius, p_radius); +} + +void ParticlesMaterial::set_emission_ring_inner_radius(float p_radius) { + emission_ring_inner_radius = p_radius; + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_ring_inner_radius, p_radius); +} + ParticlesMaterial::EmissionShape ParticlesMaterial::get_emission_shape() const { return emission_shape; } @@ -1018,6 +1070,22 @@ int ParticlesMaterial::get_emission_point_count() const { return emission_point_count; } +Vector3 ParticlesMaterial::get_emission_ring_axis() const { + return emission_ring_axis; +} + +float ParticlesMaterial::get_emission_ring_height() const { + return emission_ring_height; +} + +float ParticlesMaterial::get_emission_ring_radius() const { + return emission_ring_radius; +} + +float ParticlesMaterial::get_emission_ring_inner_radius() const { + return emission_ring_inner_radius; +} + void ParticlesMaterial::set_gravity(const Vector3 &p_gravity) { gravity = p_gravity; Vector3 gset = gravity; @@ -1054,7 +1122,7 @@ void ParticlesMaterial::_validate_property(PropertyInfo &property) const { property.usage = PROPERTY_USAGE_NONE; } - if ((property.name == "emission_point_texture" || property.name == "emission_color_texture") && (emission_shape < EMISSION_SHAPE_POINTS)) { + if ((property.name == "emission_point_texture" || property.name == "emission_color_texture") && (emission_shape != EMISSION_SHAPE_POINTS && emission_shape != EMISSION_SHAPE_DIRECTED_POINTS)) { property.usage = PROPERTY_USAGE_NONE; } @@ -1066,6 +1134,10 @@ void ParticlesMaterial::_validate_property(PropertyInfo &property) const { property.usage = PROPERTY_USAGE_NONE; } + if (property.name.begins_with("emission_ring_") && emission_shape != EMISSION_SHAPE_RING) { + property.usage = PROPERTY_USAGE_NONE; + } + if (property.name == "sub_emitter_frequency" && sub_emitter_mode != SUB_EMITTER_CONSTANT) { property.usage = PROPERTY_USAGE_NONE; } @@ -1212,6 +1284,18 @@ void ParticlesMaterial::_bind_methods() { ClassDB::bind_method(D_METHOD("set_emission_point_count", "point_count"), &ParticlesMaterial::set_emission_point_count); ClassDB::bind_method(D_METHOD("get_emission_point_count"), &ParticlesMaterial::get_emission_point_count); + ClassDB::bind_method(D_METHOD("set_emission_ring_axis", "axis"), &ParticlesMaterial::set_emission_ring_axis); + ClassDB::bind_method(D_METHOD("get_emission_ring_axis"), &ParticlesMaterial::get_emission_ring_axis); + + ClassDB::bind_method(D_METHOD("set_emission_ring_height", "height"), &ParticlesMaterial::set_emission_ring_height); + ClassDB::bind_method(D_METHOD("get_emission_ring_height"), &ParticlesMaterial::get_emission_ring_height); + + ClassDB::bind_method(D_METHOD("set_emission_ring_radius", "radius"), &ParticlesMaterial::set_emission_ring_radius); + ClassDB::bind_method(D_METHOD("get_emission_ring_radius"), &ParticlesMaterial::get_emission_ring_radius); + + ClassDB::bind_method(D_METHOD("set_emission_ring_inner_radius", "inner_radius"), &ParticlesMaterial::set_emission_ring_inner_radius); + ClassDB::bind_method(D_METHOD("get_emission_ring_inner_radius"), &ParticlesMaterial::get_emission_ring_inner_radius); + ClassDB::bind_method(D_METHOD("get_gravity"), &ParticlesMaterial::get_gravity); ClassDB::bind_method(D_METHOD("set_gravity", "accel_vec"), &ParticlesMaterial::set_gravity); @@ -1249,13 +1333,17 @@ void ParticlesMaterial::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime_randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_lifetime_randomness", "get_lifetime_randomness"); ADD_GROUP("Emission Shape", "emission_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_shape", PROPERTY_HINT_ENUM, "Point,Sphere,Box,Points,Directed Points"), "set_emission_shape", "get_emission_shape"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_shape", PROPERTY_HINT_ENUM, "Point,Sphere,Box,Points,Directed Points,Ring"), "set_emission_shape", "get_emission_shape"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_sphere_radius", PROPERTY_HINT_RANGE, "0.01,128,0.01,or_greater"), "set_emission_sphere_radius", "get_emission_sphere_radius"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_box_extents"), "set_emission_box_extents", "get_emission_box_extents"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "emission_point_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_emission_point_texture", "get_emission_point_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "emission_normal_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_emission_normal_texture", "get_emission_normal_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "emission_color_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_emission_color_texture", "get_emission_color_texture"); ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_point_count", PROPERTY_HINT_RANGE, "0,1000000,1"), "set_emission_point_count", "get_emission_point_count"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_ring_axis"), "set_emission_ring_axis", "get_emission_ring_axis"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height"), "set_emission_ring_height", "get_emission_ring_height"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius"), "set_emission_ring_radius", "get_emission_ring_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius"); ADD_GROUP("ParticleFlags", "particle_flag_"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_align_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_rotate_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ROTATE_Y); @@ -1355,6 +1443,7 @@ void ParticlesMaterial::_bind_methods() { BIND_ENUM_CONSTANT(EMISSION_SHAPE_BOX); BIND_ENUM_CONSTANT(EMISSION_SHAPE_POINTS); BIND_ENUM_CONSTANT(EMISSION_SHAPE_DIRECTED_POINTS); + BIND_ENUM_CONSTANT(EMISSION_SHAPE_RING); BIND_ENUM_CONSTANT(EMISSION_SHAPE_MAX); BIND_ENUM_CONSTANT(SUB_EMITTER_DISABLED); @@ -1384,6 +1473,10 @@ ParticlesMaterial::ParticlesMaterial() : set_emission_shape(EMISSION_SHAPE_POINT); set_emission_sphere_radius(1); set_emission_box_extents(Vector3(1, 1, 1)); + set_emission_ring_axis(Vector3(0, 0, 1.0)); + set_emission_ring_height(1); + set_emission_ring_radius(1); + set_emission_ring_inner_radius(0); set_gravity(Vector3(0, -9.8, 0)); set_lifetime_randomness(0); diff --git a/scene/resources/particles_material.h b/scene/resources/particles_material.h index 3f874bd68c..8b0b26a3d1 100644 --- a/scene/resources/particles_material.h +++ b/scene/resources/particles_material.h @@ -74,6 +74,7 @@ public: EMISSION_SHAPE_BOX, EMISSION_SHAPE_POINTS, EMISSION_SHAPE_DIRECTED_POINTS, + EMISSION_SHAPE_RING, EMISSION_SHAPE_MAX }; @@ -195,6 +196,10 @@ private: StringName emission_texture_points; StringName emission_texture_normal; StringName emission_texture_color; + StringName emission_ring_axis; + StringName emission_ring_height; + StringName emission_ring_radius; + StringName emission_ring_inner_radius; StringName gravity; @@ -235,6 +240,10 @@ private: Ref<Texture2D> emission_point_texture; Ref<Texture2D> emission_normal_texture; Ref<Texture2D> emission_color_texture; + Vector3 emission_ring_axis; + float emission_ring_height; + float emission_ring_radius; + float emission_ring_inner_radius; int emission_point_count = 1; bool anim_loop; @@ -293,6 +302,10 @@ public: void set_emission_point_texture(const Ref<Texture2D> &p_points); void set_emission_normal_texture(const Ref<Texture2D> &p_normals); void set_emission_color_texture(const Ref<Texture2D> &p_colors); + void set_emission_ring_axis(Vector3 p_axis); + void set_emission_ring_height(float p_height); + void set_emission_ring_radius(float p_radius); + void set_emission_ring_inner_radius(float p_radius); void set_emission_point_count(int p_count); EmissionShape get_emission_shape() const; @@ -301,6 +314,10 @@ public: Ref<Texture2D> get_emission_point_texture() const; Ref<Texture2D> get_emission_normal_texture() const; Ref<Texture2D> get_emission_color_texture() const; + Vector3 get_emission_ring_axis() const; + float get_emission_ring_height() const; + float get_emission_ring_radius() const; + float get_emission_ring_inner_radius() const; int get_emission_point_count() const; void set_gravity(const Vector3 &p_gravity); diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index ee61e64ed3..5cf107e8ea 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -35,8 +35,9 @@ #include "core/io/resource_format_binary.h" #include "core/version.h" -//version 2: changed names for basis, aabb, Vectors, etc. -#define FORMAT_VERSION 2 +// Version 2: changed names for Basis, AABB, Vectors, etc. +// Version 3: new string ID for ext/subresources, breaks forward compat. +#define FORMAT_VERSION 3 #include "core/io/dir_access.h" #include "core/version.h" @@ -56,22 +57,23 @@ Ref<Resource> ResourceLoaderText::get_resource() { Error ResourceLoaderText::_parse_sub_resource_dummy(DummyReadData *p_data, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) { VariantParser::Token token; VariantParser::get_token(p_stream, token, line, r_err_str); - if (token.type != VariantParser::TK_NUMBER) { - r_err_str = "Expected number (sub-resource index)"; + if (token.type != VariantParser::TK_NUMBER && token.type != VariantParser::TK_STRING) { + r_err_str = "Expected number (old style) or string (sub-resource index)"; return ERR_PARSE_ERROR; } - int index = token.value; + String unique_id = token.value; - if (!p_data->resource_map.has(index)) { + if (!p_data->resource_map.has(unique_id)) { Ref<DummyResource> dr; dr.instantiate(); - dr->set_subindex(index); - p_data->resource_map[index] = dr; - p_data->resource_set.insert(dr); + dr->set_scene_unique_id(unique_id); + p_data->resource_map[unique_id] = dr; + uint32_t im_size = p_data->resource_index_map.size(); + p_data->resource_index_map.insert(dr, im_size); } - r_res = p_data->resource_map[index]; + r_res = p_data->resource_map[unique_id]; VariantParser::get_token(p_stream, token, line, r_err_str); if (token.type != VariantParser::TK_PARENTHESIS_CLOSE) { @@ -85,12 +87,12 @@ Error ResourceLoaderText::_parse_sub_resource_dummy(DummyReadData *p_data, Varia Error ResourceLoaderText::_parse_ext_resource_dummy(DummyReadData *p_data, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) { VariantParser::Token token; VariantParser::get_token(p_stream, token, line, r_err_str); - if (token.type != VariantParser::TK_NUMBER) { - r_err_str = "Expected number (sub-resource index)"; + if (token.type != VariantParser::TK_NUMBER && token.type != VariantParser::TK_STRING) { + r_err_str = "Expected number (old style sub-resource index) or String (ext-resource ID)"; return ERR_PARSE_ERROR; } - int id = token.value; + String id = token.value; ERR_FAIL_COND_V(!p_data->rev_external_resources.has(id), ERR_PARSE_ERROR); @@ -108,14 +110,14 @@ Error ResourceLoaderText::_parse_ext_resource_dummy(DummyReadData *p_data, Varia Error ResourceLoaderText::_parse_sub_resource(VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) { VariantParser::Token token; VariantParser::get_token(p_stream, token, line, r_err_str); - if (token.type != VariantParser::TK_NUMBER) { - r_err_str = "Expected number (sub-resource index)"; + if (token.type != VariantParser::TK_NUMBER && token.type != VariantParser::TK_STRING) { + r_err_str = "Expected number (old style sub-resource index) or string"; return ERR_PARSE_ERROR; } - int index = token.value; - ERR_FAIL_COND_V(!int_resources.has(index), ERR_INVALID_PARAMETER); - r_res = int_resources[index]; + String id = token.value; + ERR_FAIL_COND_V(!int_resources.has(id), ERR_INVALID_PARAMETER); + r_res = int_resources[id]; VariantParser::get_token(p_stream, token, line, r_err_str); if (token.type != VariantParser::TK_PARENTHESIS_CLOSE) { @@ -129,16 +131,16 @@ Error ResourceLoaderText::_parse_sub_resource(VariantParser::Stream *p_stream, R Error ResourceLoaderText::_parse_ext_resource(VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) { VariantParser::Token token; VariantParser::get_token(p_stream, token, line, r_err_str); - if (token.type != VariantParser::TK_NUMBER) { - r_err_str = "Expected number (sub-resource index)"; + if (token.type != VariantParser::TK_NUMBER && token.type != VariantParser::TK_STRING) { + r_err_str = "Expected number (old style sub-resource index) or String (ext-resource ID)"; return ERR_PARSE_ERROR; } - int id = token.value; + String id = token.value; if (!ignore_resource_parsing) { if (!ext_resources.has(id)) { - r_err_str = "Can't load cached ext-resource #" + itos(id); + r_err_str = "Can't load cached ext-resource id: " + id; return ERR_PARSE_ERROR; } @@ -409,7 +411,7 @@ Error ResourceLoaderText::load() { String path = next_tag.fields["path"]; String type = next_tag.fields["type"]; - int index = next_tag.fields["id"]; + String id = next_tag.fields["id"]; if (path.find("://") == -1 && path.is_rel_path()) { // path is relative to file being loaded, so convert to a resource path @@ -453,14 +455,14 @@ Error ResourceLoaderText::load() { } else { #ifdef TOOLS_ENABLED //remember ID for saving - res->set_id_for_path(local_path, index); + res->set_id_for_path(local_path, id); #endif } er.cache = res; } - ext_resources[index] = er; + ext_resources[id] = er; error = VariantParser::parse_tag(&stream, lines, error_text, next_tag, &rp); @@ -489,15 +491,15 @@ Error ResourceLoaderText::load() { if (!next_tag.fields.has("id")) { error = ERR_FILE_CORRUPT; - error_text = "Missing 'index' in external resource tag"; + error_text = "Missing 'id' in external resource tag"; _printerr(); return error; } String type = next_tag.fields["type"]; - int id = next_tag.fields["id"]; + String id = next_tag.fields["id"]; - String path = local_path + "::" + itos(id); + String path = local_path + "::" + id; //bool exists=ResourceCache::has(path); @@ -575,7 +577,7 @@ Error ResourceLoaderText::load() { int_resources[id] = res; //always assign int resources if (do_assign && cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { res->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE); - res->set_subindex(id); + res->set_scene_unique_id(id); } if (progress && resources_total > 0) { @@ -736,7 +738,7 @@ void ResourceLoaderText::get_dependencies(FileAccess *p_f, List<String> *p_depen if (!next_tag.fields.has("id")) { error = ERR_FILE_CORRUPT; - error_text = "Missing 'index' in external resource tag"; + error_text = "Missing 'id' in external resource tag"; _printerr(); return; } @@ -814,7 +816,7 @@ Error ResourceLoaderText::rename_dependencies(FileAccess *p_f, const String &p_p } String path = next_tag.fields["path"]; - int index = next_tag.fields["id"]; + String id = next_tag.fields["id"]; String type = next_tag.fields["type"]; bool relative = false; @@ -833,7 +835,7 @@ Error ResourceLoaderText::rename_dependencies(FileAccess *p_f, const String &p_p path = base_path.path_to_file(path); } - fw->store_line("[ext_resource path=\"" + path + "\" type=\"" + type + "\" id=" + itos(index) + "]"); + fw->store_line("[ext_resource path=\"" + path + "\" type=\"" + type + "\" id=\"" + id + "\"]"); tag_end = f->get_position(); } @@ -1015,7 +1017,7 @@ Error ResourceLoaderText::save_as_binary(FileAccess *p_f, const String &p_path) String path = next_tag.fields["path"]; String type = next_tag.fields["type"]; - int index = next_tag.fields["id"]; + String id = next_tag.fields["id"]; bs_save_unicode_string(wf.f, type); bs_save_unicode_string(wf.f, path); @@ -1025,7 +1027,7 @@ Error ResourceLoaderText::save_as_binary(FileAccess *p_f, const String &p_path) dr.instantiate(); dr->set_path("res://dummy" + itos(lindex)); //anything is good to detect it for saving as external dummy_read.external_resources[dr] = lindex; - dummy_read.rev_external_resources[index] = dr; + dummy_read.rev_external_resources[id] = dr; error = VariantParser::parse_tag(&stream, lines, error_text, next_tag, &rp); @@ -1069,7 +1071,7 @@ Error ResourceLoaderText::save_as_binary(FileAccess *p_f, const String &p_path) if (!next_tag.fields.has("id")) { error = ERR_FILE_CORRUPT; - error_text = "Missing 'index' in external resource tag"; + error_text = "Missing 'id' in external resource tag"; _printerr(); return error; } @@ -1114,7 +1116,7 @@ Error ResourceLoaderText::save_as_binary(FileAccess *p_f, const String &p_path) if (assign != String()) { Map<StringName, int> empty_string_map; //unused bs_save_unicode_string(wf2, assign, true); - ResourceFormatSaverBinaryInstance::write_variant(wf2, value, dummy_read.resource_set, dummy_read.external_resources, empty_string_map); + ResourceFormatSaverBinaryInstance::write_variant(wf2, value, dummy_read.resource_index_map, dummy_read.external_resources, empty_string_map); prop_count++; } else if (next_tag.name != String()) { @@ -1175,7 +1177,7 @@ Error ResourceLoaderText::save_as_binary(FileAccess *p_f, const String &p_path) Map<StringName, int> empty_string_map; //unused bs_save_unicode_string(wf2, name, true); - ResourceFormatSaverBinaryInstance::write_variant(wf2, value, dummy_read.resource_set, dummy_read.external_resources, empty_string_map); + ResourceFormatSaverBinaryInstance::write_variant(wf2, value, dummy_read.resource_index_map, dummy_read.external_resources, empty_string_map); prop_count++; } @@ -1394,10 +1396,10 @@ String ResourceFormatSaverTextInstance::_write_resources(void *ud, const RES &p_ String ResourceFormatSaverTextInstance::_write_resource(const RES &res) { if (external_resources.has(res)) { - return "ExtResource( " + itos(external_resources[res]) + " )"; + return "ExtResource( \"" + external_resources[res] + "\" )"; } else { if (internal_resources.has(res)) { - return "SubResource( " + itos(internal_resources[res]) + " )"; + return "SubResource( \"" + internal_resources[res] + "\" )"; } else if (res->get_path().length() && res->get_path().find("::") == -1) { if (res->get_path() == local_path) { //circular reference attempt return "null"; @@ -1426,8 +1428,11 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant, ERR_PRINT("Circular reference to resource being saved found: '" + local_path + "' will be null next time it's loaded."); return; } - int index = external_resources.size(); - external_resources[res] = index; + + // Use a numeric ID as a base, because they are sorted in natural order before saving. + // This increases the chances of thread loading to fetch them first. + String id = itos(external_resources.size() + 1) + "_" + Resource::generate_scene_unique_id(); + external_resources[res] = id; return; } @@ -1513,11 +1518,11 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r takeover_paths = false; } - // save resources + // Save resources. _find_resources(p_resource, true); if (packed_scene.is_valid()) { - //add instances to external resources if saving a packed scene + // Add instances to external resources if saving a packed scene. for (int i = 0; i < packed_scene->get_state()->get_node_count(); i++) { if (packed_scene->get_state()->is_node_instance_placeholder(i)) { continue; @@ -1525,8 +1530,8 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r Ref<PackedScene> instance = packed_scene->get_state()->get_node_instance(i); if (instance.is_valid() && !external_resources.has(instance)) { - int index = external_resources.size(); - external_resources[instance] = index; + int index = external_resources.size() + 1; + external_resources[instance] = itos(index) + "_" + Resource::generate_scene_unique_id(); // Keep the order for improved thread loading performance. } } } @@ -1537,12 +1542,6 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r title += "type=\"" + p_resource->get_class() + "\" "; } int load_steps = saved_resources.size() + external_resources.size(); - /* - if (packed_scene.is_valid()) { - load_steps+=packed_scene->get_node_count(); - } - //no, better to not use load steps from nodes, no point to that - */ if (load_steps > 1) { title += "load_steps=" + itos(load_steps) + " "; @@ -1550,51 +1549,61 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r title += "format=" + itos(FORMAT_VERSION) + ""; f->store_string(title); - f->store_line("]\n"); //one empty line + f->store_line("]\n"); // One empty line. } #ifdef TOOLS_ENABLED - //keep order from cached ids - Set<int> cached_ids_found; - for (Map<RES, int>::Element *E = external_resources.front(); E; E = E->next()) { - int cached_id = E->key()->get_id_for_path(local_path); - if (cached_id < 0 || cached_ids_found.has(cached_id)) { - E->get() = -1; //reset + // Keep order from cached ids. + Set<String> cached_ids_found; + for (Map<RES, String>::Element *E = external_resources.front(); E; E = E->next()) { + String cached_id = E->key()->get_id_for_path(local_path); + if (cached_id == "" || cached_ids_found.has(cached_id)) { + int sep_pos = E->get().find("_"); + if (sep_pos != -1) { + E->get() = E->get().substr(0, sep_pos + 1); // Keep the order found, for improved thread loading performance. + } else { + E->get() = ""; + } + } else { E->get() = cached_id; cached_ids_found.insert(cached_id); } } - //create IDs for non cached resources - for (Map<RES, int>::Element *E = external_resources.front(); E; E = E->next()) { - if (cached_ids_found.has(E->get())) { //already cached, go on + // Create IDs for non cached resources. + for (Map<RES, String>::Element *E = external_resources.front(); E; E = E->next()) { + if (cached_ids_found.has(E->get())) { // Already cached, go on. continue; } - int attempt = 1; //start from one, more readable format - while (cached_ids_found.has(attempt)) { - attempt++; + String attempt; + while (true) { + attempt = E->get() + Resource::generate_scene_unique_id(); + if (!cached_ids_found.has(attempt)) { + break; + } } cached_ids_found.insert(attempt); E->get() = attempt; - //update also in resource + // Update also in resource. Ref<Resource> res = E->key(); res->set_id_for_path(local_path, attempt); } #else - //make sure to start from one, as it makes format more readable - for (Map<RES, int>::Element *E = external_resources.front(); E; E = E->next()) { - E->get() = E->get() + 1; + // Make sure to start from one, as it makes format more readable. + int counter = 1; + for (Map<RES, String>::Element *E = external_resources.front(); E; E = E->next()) { + E->get() = itos(counter++); } #endif Vector<ResourceSort> sorted_er; - for (Map<RES, int>::Element *E = external_resources.front(); E; E = E->next()) { + for (Map<RES, String>::Element *E = external_resources.front(); E; E = E->next()) { ResourceSort rs; rs.resource = E->key(); - rs.index = E->get(); + rs.id = E->get(); sorted_er.push_back(rs); } @@ -1603,23 +1612,23 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r for (int i = 0; i < sorted_er.size(); i++) { String p = sorted_er[i].resource->get_path(); - f->store_string("[ext_resource path=\"" + p + "\" type=\"" + sorted_er[i].resource->get_save_class() + "\" id=" + itos(sorted_er[i].index) + "]\n"); //bundled + f->store_string("[ext_resource path=\"" + p + "\" type=\"" + sorted_er[i].resource->get_save_class() + "\" id=\"" + sorted_er[i].id + "\"]\n"); // Bundled. } if (external_resources.size()) { - f->store_line(String()); //separate + f->store_line(String()); // Separate. } - Set<int> used_indices; + Set<String> used_unique_ids; for (List<RES>::Element *E = saved_resources.front(); E; E = E->next()) { RES res = E->get(); if (E->next() && (res->get_path() == "" || res->get_path().find("::") != -1)) { - if (res->get_subindex() != 0) { - if (used_indices.has(res->get_subindex())) { - res->set_subindex(0); //repeated + if (res->get_scene_unique_id() != "") { + if (used_unique_ids.has(res->get_scene_unique_id())) { + res->set_scene_unique_id(""); // Repeated. } else { - used_indices.insert(res->get_subindex()); + used_unique_ids.insert(res->get_scene_unique_id()); } } } @@ -1631,31 +1640,35 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r bool main = (E->next() == nullptr); if (main && packed_scene.is_valid()) { - break; //save as a scene + break; // Save as a scene. } if (main) { f->store_line("[resource]"); } else { String line = "[sub_resource "; - if (res->get_subindex() == 0) { - int new_subindex = 1; - if (used_indices.size()) { - new_subindex = used_indices.back()->get() + 1; + if (res->get_scene_unique_id() == "") { + String new_id; + while (true) { + new_id = res->get_class() + "_" + Resource::generate_scene_unique_id(); + + if (!used_unique_ids.has(new_id)) { + break; + } } - res->set_subindex(new_subindex); - used_indices.insert(new_subindex); + res->set_scene_unique_id(new_id); + used_unique_ids.insert(new_id); } - int idx = res->get_subindex(); - line += "type=\"" + res->get_class() + "\" id=" + itos(idx); - f->store_line(line + "]"); + String id = res->get_scene_unique_id(); + line += "type=\"" + res->get_class() + "\" id=\"" + id; + f->store_line(line + "\"]"); if (takeover_paths) { - res->set_path(p_path + "::" + itos(idx), true); + res->set_path(p_path + "::" + id, true); } - internal_resources[res] = idx; + internal_resources[res] = id; #ifdef TOOLS_ENABLED res->set_edited(false); #endif @@ -1663,7 +1676,6 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r List<PropertyInfo> property_list; res->get_property_list(&property_list); - //property_list.sort(); for (List<PropertyInfo>::Element *PE = property_list.front(); PE; PE = PE->next()) { if (skip_editor && PE->get().name.begins_with("__editor")) { continue; @@ -1704,7 +1716,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r } if (packed_scene.is_valid()) { - //if this is a scene, save nodes and connections! + // If this is a scene, save nodes and connections! Ref<SceneState> state = packed_scene->get_state(); for (int i = 0; i < state->get_node_count(); i++) { StringName type = state->get_node_type(i); @@ -1812,7 +1824,6 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r } f->close(); - //memdelete(f); return OK; } diff --git a/scene/resources/resource_format_text.h b/scene/resources/resource_format_text.h index f5d9cca859..5173619a17 100644 --- a/scene/resources/resource_format_text.h +++ b/scene/resources/resource_format_text.h @@ -58,10 +58,8 @@ class ResourceLoaderText { bool ignore_resource_parsing = false; - //Map<String,String> remaps; - - Map<int, ExtResource> ext_resources; - Map<int, RES> int_resources; + Map<String, ExtResource> ext_resources; + Map<String, RES> int_resources; int resources_total = 0; int resource_current = 0; @@ -77,7 +75,6 @@ class ResourceLoaderText { mutable int lines = 0; Map<String, String> remaps; - //void _printerr(); static Error _parse_sub_resources(void *p_self, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) { return reinterpret_cast<ResourceLoaderText *>(p_self)->_parse_sub_resource(p_stream, r_res, line, r_err_str); } static Error _parse_ext_resources(void *p_self, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) { return reinterpret_cast<ResourceLoaderText *>(p_self)->_parse_ext_resource(p_stream, r_res, line, r_err_str); } @@ -92,9 +89,9 @@ class ResourceLoaderText { struct DummyReadData { Map<RES, int> external_resources; - Map<int, RES> rev_external_resources; - Set<RES> resource_set; - Map<int, RES> resource_map; + Map<String, RES> rev_external_resources; + Map<RES, int> resource_index_map; + Map<String, RES> resource_map; }; static Error _parse_sub_resource_dummys(void *p_self, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) { return _parse_sub_resource_dummy((DummyReadData *)(p_self), p_stream, r_res, line, r_err_str); } @@ -168,14 +165,14 @@ class ResourceFormatSaverTextInstance { Set<RES> resource_set; List<RES> saved_resources; - Map<RES, int> external_resources; - Map<RES, int> internal_resources; + Map<RES, String> external_resources; + Map<RES, String> internal_resources; struct ResourceSort { RES resource; - int index = 0; + String id; bool operator<(const ResourceSort &p_right) const { - return index < p_right.index; + return id.naturalnocasecmp_to(p_right.id) < 0; } }; diff --git a/scene/resources/sky_material.cpp b/scene/resources/sky_material.cpp index b34d3feb47..ec00f9d7b7 100644 --- a/scene/resources/sky_material.cpp +++ b/scene/resources/sky_material.cpp @@ -30,6 +30,9 @@ #include "sky_material.h" +Mutex ProceduralSkyMaterial::shader_mutex; +RID ProceduralSkyMaterial::shader; + void ProceduralSkyMaterial::set_sky_top_color(const Color &p_sky_top) { sky_top_color = p_sky_top; RS::get_singleton()->material_set_param(_get_material(), "sky_top_color", sky_top_color.to_linear()); @@ -128,7 +131,17 @@ Shader::Mode ProceduralSkyMaterial::get_shader_mode() const { return Shader::MODE_SKY; } +RID ProceduralSkyMaterial::get_rid() const { + _update_shader(); + if (!shader_set) { + RS::get_singleton()->material_set_shader(_get_material(), shader); + shader_set = true; + } + return _get_material(); +} + RID ProceduralSkyMaterial::get_shader_rid() const { + _update_shader(); return shader; } @@ -180,10 +193,18 @@ void ProceduralSkyMaterial::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sun_curve", PROPERTY_HINT_EXP_EASING), "set_sun_curve", "get_sun_curve"); } -ProceduralSkyMaterial::ProceduralSkyMaterial() { - shader = RS::get_singleton()->shader_create(); +void ProceduralSkyMaterial::cleanup_shader() { + if (shader.is_valid()) { + RS::get_singleton()->free(shader); + } +} - RS::get_singleton()->shader_set_code(shader, R"( +void ProceduralSkyMaterial::_update_shader() { + shader_mutex.lock(); + if (shader.is_null()) { + shader = RS::get_singleton()->shader_create(); + + RS::get_singleton()->shader_set_code(shader, R"( shader_type sky; uniform vec4 sky_top_color : hint_color = vec4(0.35, 0.46, 0.71, 1.0); @@ -250,9 +271,11 @@ void sky() { COLOR = mix(ground, sky, step(0.0, EYEDIR.y)); } )"); + } + shader_mutex.unlock(); +} - RS::get_singleton()->material_set_shader(_get_material(), shader); - +ProceduralSkyMaterial::ProceduralSkyMaterial() { set_sky_top_color(Color(0.35, 0.46, 0.71)); set_sky_horizon_color(Color(0.55, 0.69, 0.81)); set_sky_curve(0.09); @@ -268,7 +291,6 @@ void sky() { } ProceduralSkyMaterial::~ProceduralSkyMaterial() { - RS::get_singleton()->free(shader); RS::get_singleton()->material_set_shader(_get_material(), RID()); } @@ -293,7 +315,17 @@ Shader::Mode PanoramaSkyMaterial::get_shader_mode() const { return Shader::MODE_SKY; } +RID PanoramaSkyMaterial::get_rid() const { + _update_shader(); + if (!shader_set) { + RS::get_singleton()->material_set_shader(_get_material(), shader); + shader_set = true; + } + return _get_material(); +} + RID PanoramaSkyMaterial::get_shader_rid() const { + _update_shader(); return shader; } @@ -304,10 +336,21 @@ void PanoramaSkyMaterial::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "panorama", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_panorama", "get_panorama"); } -PanoramaSkyMaterial::PanoramaSkyMaterial() { - shader = RS::get_singleton()->shader_create(); +Mutex PanoramaSkyMaterial::shader_mutex; +RID PanoramaSkyMaterial::shader; - RS::get_singleton()->shader_set_code(shader, R"( +void PanoramaSkyMaterial::cleanup_shader() { + if (shader.is_valid()) { + RS::get_singleton()->free(shader); + } +} + +void PanoramaSkyMaterial::_update_shader() { + shader_mutex.lock(); + if (shader.is_null()) { + shader = RS::get_singleton()->shader_create(); + + RS::get_singleton()->shader_set_code(shader, R"( shader_type sky; uniform sampler2D source_panorama : filter_linear; @@ -316,12 +359,15 @@ void sky() { COLOR = texture(source_panorama, SKY_COORDS).rgb; } )"); + } - RS::get_singleton()->material_set_shader(_get_material(), shader); + shader_mutex.unlock(); +} + +PanoramaSkyMaterial::PanoramaSkyMaterial() { } PanoramaSkyMaterial::~PanoramaSkyMaterial() { - RS::get_singleton()->free(shader); RS::get_singleton()->material_set_shader(_get_material(), RID()); } @@ -436,10 +482,23 @@ Shader::Mode PhysicalSkyMaterial::get_shader_mode() const { return Shader::MODE_SKY; } +RID PhysicalSkyMaterial::get_rid() const { + _update_shader(); + if (!shader_set) { + RS::get_singleton()->material_set_shader(_get_material(), shader); + shader_set = true; + } + return _get_material(); +} + RID PhysicalSkyMaterial::get_shader_rid() const { + _update_shader(); return shader; } +Mutex PhysicalSkyMaterial::shader_mutex; +RID PhysicalSkyMaterial::shader; + void PhysicalSkyMaterial::_bind_methods() { ClassDB::bind_method(D_METHOD("set_rayleigh_coefficient", "rayleigh"), &PhysicalSkyMaterial::set_rayleigh_coefficient); ClassDB::bind_method(D_METHOD("get_rayleigh_coefficient"), &PhysicalSkyMaterial::get_rayleigh_coefficient); @@ -491,10 +550,18 @@ void PhysicalSkyMaterial::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "night_sky", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_night_sky", "get_night_sky"); } -PhysicalSkyMaterial::PhysicalSkyMaterial() { - shader = RS::get_singleton()->shader_create(); +void PhysicalSkyMaterial::cleanup_shader() { + if (shader.is_valid()) { + RS::get_singleton()->free(shader); + } +} - RS::get_singleton()->shader_set_code(shader, R"( +void PhysicalSkyMaterial::_update_shader() { + shader_mutex.lock(); + if (shader.is_null()) { + shader = RS::get_singleton()->shader_create(); + + RS::get_singleton()->shader_set_code(shader, R"( shader_type sky; uniform float rayleigh : hint_range(0, 64) = 2.0; @@ -588,9 +655,12 @@ void sky() { } } )"); + } - RS::get_singleton()->material_set_shader(_get_material(), shader); + shader_mutex.unlock(); +} +PhysicalSkyMaterial::PhysicalSkyMaterial() { set_rayleigh_coefficient(2.0); set_rayleigh_color(Color(0.056, 0.14, 0.3)); set_mie_coefficient(0.005); @@ -604,5 +674,4 @@ void sky() { } PhysicalSkyMaterial::~PhysicalSkyMaterial() { - RS::get_singleton()->free(shader); } diff --git a/scene/resources/sky_material.h b/scene/resources/sky_material.h index 8fe015519d..63e730617b 100644 --- a/scene/resources/sky_material.h +++ b/scene/resources/sky_material.h @@ -51,7 +51,10 @@ private: float sun_angle_max; float sun_curve; - RID shader; + static Mutex shader_mutex; + static RID shader; + static void _update_shader(); + mutable bool shader_set = false; protected: static void _bind_methods(); @@ -90,6 +93,9 @@ public: virtual Shader::Mode get_shader_mode() const override; virtual RID get_shader_rid() const override; + virtual RID get_rid() const override; + + static void cleanup_shader(); ProceduralSkyMaterial(); ~ProceduralSkyMaterial(); @@ -103,7 +109,11 @@ class PanoramaSkyMaterial : public Material { private: Ref<Texture2D> panorama; - RID shader; + + static Mutex shader_mutex; + static RID shader; + static void _update_shader(); + mutable bool shader_set = false; protected: static void _bind_methods(); @@ -115,6 +125,9 @@ public: virtual Shader::Mode get_shader_mode() const override; virtual RID get_shader_rid() const override; + virtual RID get_rid() const override; + + static void cleanup_shader(); PanoramaSkyMaterial(); ~PanoramaSkyMaterial(); @@ -127,7 +140,8 @@ class PhysicalSkyMaterial : public Material { GDCLASS(PhysicalSkyMaterial, Material); private: - RID shader; + static Mutex shader_mutex; + static RID shader; float rayleigh; Color rayleigh_color; @@ -140,6 +154,8 @@ private: float exposure; float dither_strength; Ref<Texture2D> night_sky; + static void _update_shader(); + mutable bool shader_set = false; protected: static void _bind_methods(); @@ -182,6 +198,9 @@ public: virtual Shader::Mode get_shader_mode() const override; virtual RID get_shader_rid() const override; + static void cleanup_shader(); + virtual RID get_rid() const override; + PhysicalSkyMaterial(); ~PhysicalSkyMaterial(); }; diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index deb5a50eb2..f9b5a4cfec 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -41,6 +41,8 @@ /////////////////////////////// TileSet ////////////////////////////////////// +const int TileSet::INVALID_SOURCE = -1; + const char *TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[] = { "right_side", "right_corner", @@ -127,8 +129,8 @@ void TileSet::_compute_next_source_id() { // Sources management int TileSet::add_source(Ref<TileSetSource> p_tile_set_source, int p_atlas_source_id_override) { - ERR_FAIL_COND_V(!p_tile_set_source.is_valid(), -1); - ERR_FAIL_COND_V_MSG(p_atlas_source_id_override >= 0 && (sources.has(p_atlas_source_id_override)), -1, vformat("Cannot create TileSet atlas source. Another atlas source exists with id %d.", p_atlas_source_id_override)); + ERR_FAIL_COND_V(!p_tile_set_source.is_valid(), TileSet::INVALID_SOURCE); + ERR_FAIL_COND_V_MSG(p_atlas_source_id_override >= 0 && (sources.has(p_atlas_source_id_override)), TileSet::INVALID_SOURCE, vformat("Cannot create TileSet atlas source. Another atlas source exists with id %d.", p_atlas_source_id_override)); int new_source_id = p_atlas_source_id_override >= 0 ? p_atlas_source_id_override : next_source_id; sources[new_source_id] = p_tile_set_source; @@ -191,7 +193,7 @@ int TileSet::get_source_count() const { } int TileSet::get_source_id(int p_index) const { - ERR_FAIL_INDEX_V(p_index, source_ids.size(), -1); + ERR_FAIL_INDEX_V(p_index, source_ids.size(), TileSet::INVALID_SOURCE); return source_ids[p_index]; } @@ -607,6 +609,254 @@ Variant::Type TileSet::get_custom_data_type(int p_layer_id) const { return custom_data_layers[p_layer_id].type; } +void TileSet::set_source_level_tile_proxy(int p_source_from, int p_source_to) { + ERR_FAIL_COND(p_source_from == TileSet::INVALID_SOURCE || p_source_to == TileSet::INVALID_SOURCE); + + source_level_proxies[p_source_from] = p_source_to; + + emit_changed(); +} + +int TileSet::get_source_level_tile_proxy(int p_source_from) { + ERR_FAIL_COND_V(!source_level_proxies.has(p_source_from), TileSet::INVALID_SOURCE); + + return source_level_proxies[p_source_from]; +} + +bool TileSet::has_source_level_tile_proxy(int p_source_from) { + return source_level_proxies.has(p_source_from); +} + +void TileSet::remove_source_level_tile_proxy(int p_source_from) { + ERR_FAIL_COND(!source_level_proxies.has(p_source_from)); + + source_level_proxies.erase(p_source_from); + + emit_changed(); +} + +void TileSet::set_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_source_to, Vector2i p_coords_to) { + ERR_FAIL_COND(p_source_from == TileSet::INVALID_SOURCE || p_source_to == TileSet::INVALID_SOURCE); + ERR_FAIL_COND(p_coords_from == TileSetSource::INVALID_ATLAS_COORDS || p_coords_to == TileSetSource::INVALID_ATLAS_COORDS); + + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + + Array to; + to.push_back(p_source_to); + to.push_back(p_coords_to); + + coords_level_proxies[from] = to; + + emit_changed(); +} + +Array TileSet::get_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from) { + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + + ERR_FAIL_COND_V(!coords_level_proxies.has(from), Array()); + + return coords_level_proxies[from]; +} + +bool TileSet::has_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from) { + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + + return coords_level_proxies.has(from); +} + +void TileSet::remove_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from) { + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + + ERR_FAIL_COND(!coords_level_proxies.has(from)); + + coords_level_proxies.erase(from); + + emit_changed(); +} + +void TileSet::set_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from, int p_source_to, Vector2i p_coords_to, int p_alternative_to) { + ERR_FAIL_COND(p_source_from == TileSet::INVALID_SOURCE || p_source_to == TileSet::INVALID_SOURCE); + ERR_FAIL_COND(p_coords_from == TileSetSource::INVALID_ATLAS_COORDS || p_coords_to == TileSetSource::INVALID_ATLAS_COORDS); + + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + from.push_back(p_alternative_from); + + Array to; + to.push_back(p_source_to); + to.push_back(p_coords_to); + to.push_back(p_alternative_to); + + alternative_level_proxies[from] = to; + + emit_changed(); +} + +Array TileSet::get_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from) { + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + from.push_back(p_alternative_from); + + ERR_FAIL_COND_V(!alternative_level_proxies.has(from), Array()); + + return alternative_level_proxies[from]; +} + +bool TileSet::has_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from) { + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + from.push_back(p_alternative_from); + + return alternative_level_proxies.has(from); +} + +void TileSet::remove_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from) { + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + from.push_back(p_alternative_from); + + ERR_FAIL_COND(!alternative_level_proxies.has(from)); + + alternative_level_proxies.erase(from); + + emit_changed(); +} + +Array TileSet::get_source_level_tile_proxies() const { + Array output; + for (Map<int, int>::Element *E = source_level_proxies.front(); E; E = E->next()) { + Array proxy; + proxy.push_back(E->key()); + proxy.push_back(E->get()); + output.push_back(proxy); + } + return output; +} + +Array TileSet::get_coords_level_tile_proxies() const { + Array output; + for (Map<Array, Array>::Element *E = coords_level_proxies.front(); E; E = E->next()) { + Array proxy; + proxy.append_array(E->key()); + proxy.append_array(E->get()); + output.push_back(proxy); + } + return output; +} + +Array TileSet::get_alternative_level_tile_proxies() const { + Array output; + for (Map<Array, Array>::Element *E = alternative_level_proxies.front(); E; E = E->next()) { + Array proxy; + proxy.append_array(E->key()); + proxy.append_array(E->get()); + output.push_back(proxy); + } + return output; +} + +Array TileSet::map_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from) const { + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + from.push_back(p_alternative_from); + + // Check if the tile is valid, and if so, don't map the tile and return the input. + if (has_source(p_source_from)) { + Ref<TileSetSource> source = get_source(p_source_from); + if (source->has_tile(p_coords_from) && source->has_alternative_tile(p_coords_from, p_alternative_from)) { + return from; + } + } + + // Source, coords and alternative match. + if (alternative_level_proxies.has(from)) { + return alternative_level_proxies[from].duplicate(); + } + + // Source and coords match. + from.pop_back(); + if (coords_level_proxies.has(from)) { + Array output = coords_level_proxies[from].duplicate(); + output.push_back(p_alternative_from); + return output; + } + + // Source matches. + if (source_level_proxies.has(p_source_from)) { + Array output; + output.push_back(source_level_proxies[p_source_from]); + output.push_back(p_coords_from); + output.push_back(p_alternative_from); + return output; + } + + Array output; + output.push_back(p_source_from); + output.push_back(p_coords_from); + output.push_back(p_alternative_from); + return output; +} + +void TileSet::cleanup_invalid_tile_proxies() { + // Source level. + Vector<int> source_to_remove; + for (Map<int, int>::Element *E = source_level_proxies.front(); E; E = E->next()) { + if (has_source(E->key())) { + source_to_remove.append(E->key()); + } + } + for (int i = 0; i < source_to_remove.size(); i++) { + remove_source_level_tile_proxy(source_to_remove[i]); + } + + // Coords level. + Vector<Array> coords_to_remove; + for (Map<Array, Array>::Element *E = coords_level_proxies.front(); E; E = E->next()) { + Array a = E->key(); + if (has_source(a[0]) && get_source(a[0])->has_tile(a[1])) { + coords_to_remove.append(a); + } + } + for (int i = 0; i < coords_to_remove.size(); i++) { + Array a = coords_to_remove[i]; + remove_coords_level_tile_proxy(a[0], a[1]); + } + + // Alternative level. + Vector<Array> alternative_to_remove; + for (Map<Array, Array>::Element *E = alternative_level_proxies.front(); E; E = E->next()) { + Array a = E->key(); + if (has_source(a[0]) && get_source(a[0])->has_tile(a[1]) && get_source(a[0])->has_alternative_tile(a[1], a[2])) { + alternative_to_remove.append(a); + } + } + for (int i = 0; i < alternative_to_remove.size(); i++) { + Array a = alternative_to_remove[i]; + remove_alternative_level_tile_proxy(a[0], a[1], a[2]); + } +} + +void TileSet::clear_tile_proxies() { + source_level_proxies.clear(); + coords_level_proxies.clear(); + alternative_level_proxies.clear(); + + emit_changed(); +} + Vector<Vector2> TileSet::get_tile_shape_polygon() { Vector<Vector2> points; if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { @@ -1539,7 +1789,7 @@ const Vector2i TileSetSource::INVALID_ATLAS_COORDS = Vector2i(-1, -1); const int TileSetSource::INVALID_TILE_ALTERNATIVE = -1; #ifndef DISABLE_DEPRECATED -void TileSet::compatibility_conversion() { +void TileSet::_compatibility_conversion() { for (Map<int, CompatibilityTileData *>::Element *E = compatibility_data.front(); E; E = E->next()) { CompatibilityTileData *ctd = E->value(); @@ -1551,13 +1801,93 @@ void TileSet::compatibility_conversion() { // Handle each tile as a new source. Not optimal but at least it should stay compatible. switch (ctd->tile_mode) { - case 0: // SINGLE_TILE - // TODO - break; - case 1: // AUTO_TILE - // TODO - break; - case 2: // ATLAS_TILE + case COMPATIBILITY_TILE_MODE_SINGLE_TILE: { + atlas_source->set_margins(ctd->region.get_position()); + atlas_source->set_texture_region_size(ctd->region.get_size()); + + Vector2i coords; + for (int flags = 0; flags < 8; flags++) { + bool flip_h = flags & 1; + bool flip_v = flags & 2; + bool transpose = flags & 4; + + int alternative_tile = 0; + if (!atlas_source->has_tile(coords)) { + atlas_source->create_tile(coords); + } else { + alternative_tile = atlas_source->create_alternative_tile(coords); + } + + // Add to the mapping. + Array key_array; + key_array.push_back(flip_h); + key_array.push_back(flip_v); + key_array.push_back(transpose); + + Array value_array; + value_array.push_back(source_id); + value_array.push_back(coords); + value_array.push_back(alternative_tile); + + if (!compatibility_tilemap_mapping.has(E->key())) { + compatibility_tilemap_mapping[E->key()] = Map<Array, Array>(); + } + compatibility_tilemap_mapping[E->key()][key_array] = value_array; + compatibility_tilemap_mapping_tile_modes[E->key()] = COMPATIBILITY_TILE_MODE_SINGLE_TILE; + + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(coords, alternative_tile)); + + tile_data->set_flip_h(flip_h); + tile_data->set_flip_v(flip_v); + tile_data->set_transpose(transpose); + tile_data->tile_set_material(ctd->material); + tile_data->set_modulate(ctd->modulate); + tile_data->set_z_index(ctd->z_index); + + if (ctd->occluder.is_valid()) { + if (get_occlusion_layers_count() < 1) { + set_occlusion_layers_count(1); + } + tile_data->set_occluder(0, ctd->occluder); + } + if (ctd->navigation.is_valid()) { + if (get_navigation_layers_count() < 1) { + set_navigation_layers_count(1); + } + tile_data->set_navigation_polygon(0, ctd->autotile_navpoly_map[coords]); + } + + tile_data->set_z_index(ctd->z_index); + + // Add the shapes. + if (ctd->shapes.size() > 0) { + if (get_physics_layers_count() < 1) { + set_physics_layers_count(1); + } + } + for (int k = 0; k < ctd->shapes.size(); k++) { + CompatibilityShapeData csd = ctd->shapes[k]; + if (csd.autotile_coords == coords) { + Ref<ConvexPolygonShape2D> convex_shape = csd.shape; // Only ConvexPolygonShape2D are supported, which is the default type used by the 3.x editor + if (convex_shape.is_valid()) { + Vector<Vector2> polygon = convex_shape->get_points(); + for (int point_index = 0; point_index < polygon.size(); point_index++) { + polygon.write[point_index] = csd.transform.xform(polygon[point_index]); + } + tile_data->set_collision_polygons_count(0, tile_data->get_collision_polygons_count(0) + 1); + int index = tile_data->get_collision_polygons_count(0) - 1; + tile_data->set_collision_polygon_one_way(0, index, csd.one_way); + tile_data->set_collision_polygon_one_way_margin(0, index, csd.one_way_margin); + tile_data->set_collision_polygon_points(0, index, polygon); + } + } + } + } + } break; + case COMPATIBILITY_TILE_MODE_AUTO_TILE: { + // Not supported. It would need manual conversion. + } break; + case COMPATIBILITY_TILE_MODE_ATLAS_TILE: { atlas_source->set_margins(ctd->region.get_position()); atlas_source->set_separation(Vector2i(ctd->autotile_spacing, ctd->autotile_spacing)); atlas_source->set_texture_region_size(ctd->autotile_tile_size); @@ -1578,6 +1908,25 @@ void TileSet::compatibility_conversion() { } else { alternative_tile = atlas_source->create_alternative_tile(coords); } + + // Add to the mapping. + Array key_array; + key_array.push_back(coords); + key_array.push_back(flip_h); + key_array.push_back(flip_v); + key_array.push_back(transpose); + + Array value_array; + value_array.push_back(source_id); + value_array.push_back(coords); + value_array.push_back(alternative_tile); + + if (!compatibility_tilemap_mapping.has(E->key())) { + compatibility_tilemap_mapping[E->key()] = Map<Array, Array>(); + } + compatibility_tilemap_mapping[E->key()][key_array] = value_array; + compatibility_tilemap_mapping_tile_modes[E->key()] = COMPATIBILITY_TILE_MODE_ATLAS_TILE; + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(coords, alternative_tile)); tile_data->set_flip_h(flip_h); @@ -1641,7 +1990,7 @@ void TileSet::compatibility_conversion() { } } } - break; + } break; } // Offset all shapes @@ -1655,9 +2004,6 @@ void TileSet::compatibility_conversion() { convex->set_points(points); } } - - // Add the mapping to the map - compatibility_source_mapping.insert(E->key(), source_id); } // Reset compatibility data @@ -1666,14 +2012,50 @@ void TileSet::compatibility_conversion() { } compatibility_data = Map<int, CompatibilityTileData *>(); } + +Array TileSet::compatibility_tilemap_map(int p_tile_id, Vector2i p_coords, bool p_flip_h, bool p_flip_v, bool p_transpose) { + Array cannot_convert_array; + cannot_convert_array.push_back(TileSet::INVALID_SOURCE); + cannot_convert_array.push_back(TileSetAtlasSource::INVALID_ATLAS_COORDS); + cannot_convert_array.push_back(TileSetAtlasSource::INVALID_TILE_ALTERNATIVE); + + if (!compatibility_tilemap_mapping.has(p_tile_id)) { + return cannot_convert_array; + } + + int tile_mode = compatibility_tilemap_mapping_tile_modes[p_tile_id]; + switch (tile_mode) { + case COMPATIBILITY_TILE_MODE_SINGLE_TILE: { + Array a; + a.push_back(p_flip_h); + a.push_back(p_flip_v); + a.push_back(p_transpose); + return compatibility_tilemap_mapping[p_tile_id][a]; + } + case COMPATIBILITY_TILE_MODE_AUTO_TILE: + return cannot_convert_array; + break; + case COMPATIBILITY_TILE_MODE_ATLAS_TILE: { + Array a; + a.push_back(p_coords); + a.push_back(p_flip_h); + a.push_back(p_flip_v); + a.push_back(p_transpose); + return compatibility_tilemap_mapping[p_tile_id][a]; + } + default: + return cannot_convert_array; + break; + } +}; + #endif // DISABLE_DEPRECATED bool TileSet::_set(const StringName &p_name, const Variant &p_value) { Vector<String> components = String(p_name).split("/", true, 2); #ifndef DISABLE_DEPRECATED - // TODO: THIS IS HOW WE CHECK IF WE HAVE A DEPRECATED RESOURCE - // This should be moved to a dedicated conversion system + // TODO: This should be moved to a dedicated conversion system (see #50691) if (components.size() >= 1 && components[0].is_valid_int()) { int id = components[0].to_int(); @@ -1809,29 +2191,23 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { /* // IGNORED FOR NOW, they seem duplicated data compared to the shapes array } else if (what == "shape") { - // TODO } else if (what == "shape_offset") { - // TODO } else if (what == "shape_transform") { - // TODO } else if (what == "shape_one_way") { - // TODO } else if (what == "shape_one_way_margin") { - // TODO } // IGNORED FOR NOW, maybe useless ? else if (what == "occluder_offset") { // Not } else if (what == "navigation_offset") { - // TODO } */ } else if (what == "z_index") { ctd->z_index = p_value; - // TODO: remove the conversion from here, it's not where it should be done - compatibility_conversion(); + // TODO: remove the conversion from here, it's not where it should be done (see #50691) + _compatibility_conversion(); } else { return false; } @@ -1966,6 +2342,31 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { add_source(p_value, source_id); } return true; + } else if (components.size() == 2 && components[0] == "tile_proxies") { + ERR_FAIL_COND_V(p_value.get_type() != Variant::ARRAY, false); + Array a = p_value; + ERR_FAIL_COND_V(a.size() % 2 != 0, false); + if (components[1] == "source_level") { + for (int i = 0; i < a.size(); i += 2) { + set_source_level_tile_proxy(a[i], a[i + 1]); + } + return true; + } else if (components[1] == "coords_level") { + for (int i = 0; i < a.size(); i += 2) { + Array key = a[i]; + Array value = a[i + 1]; + set_coords_level_tile_proxy(key[0], key[1], value[0], value[1]); + } + return true; + } else if (components[1] == "alternative_level") { + for (int i = 0; i < a.size(); i += 2) { + Array key = a[i]; + Array value = a[i + 1]; + set_alternative_level_tile_proxy(key[0], key[1], key[2], value[0], value[1], value[2]); + } + return true; + } + return false; } #ifndef DISABLE_DEPRECATED @@ -2065,6 +2466,33 @@ bool TileSet::_get(const StringName &p_name, Variant &r_ret) const { } else { return false; } + } else if (components.size() == 2 && components[0] == "tile_proxies") { + if (components[1] == "source_level") { + Array a; + for (Map<int, int>::Element *E = source_level_proxies.front(); E; E = E->next()) { + a.push_back(E->key()); + a.push_back(E->get()); + } + r_ret = a; + return true; + } else if (components[1] == "coords_level") { + Array a; + for (Map<Array, Array>::Element *E = coords_level_proxies.front(); E; E = E->next()) { + a.push_back(E->key()); + a.push_back(E->get()); + } + r_ret = a; + return true; + } else if (components[1] == "alternative_level") { + Array a; + for (Map<Array, Array>::Element *E = alternative_level_proxies.front(); E; E = E->next()) { + a.push_back(E->key()); + a.push_back(E->get()); + } + r_ret = a; + return true; + } + return false; } return false; @@ -2138,12 +2566,19 @@ void TileSet::_get_property_list(List<PropertyInfo> *p_list) const { for (Map<int, Ref<TileSetSource>>::Element *E_source = sources.front(); E_source; E_source = E_source->next()) { p_list->push_back(PropertyInfo(Variant::INT, vformat("sources/%d", E_source->key()), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); } + + // Tile Proxies. + // Note: proxies need to be set after sources are set. + p_list->push_back(PropertyInfo(Variant::NIL, "Tile Proxies", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); + p_list->push_back(PropertyInfo(Variant::ARRAY, "tile_proxies/source_level", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::ARRAY, "tile_proxies/coords_level", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::ARRAY, "tile_proxies/alternative_level", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); } void TileSet::_bind_methods() { // Sources management. ClassDB::bind_method(D_METHOD("get_next_source_id"), &TileSet::get_next_source_id); - ClassDB::bind_method(D_METHOD("add_source", "atlas_source_id_override"), &TileSet::add_source, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("add_source", "atlas_source_id_override"), &TileSet::add_source, DEFVAL(TileSet::INVALID_SOURCE)); ClassDB::bind_method(D_METHOD("remove_source", "source_id"), &TileSet::remove_source); ClassDB::bind_method(D_METHOD("set_source_id", "source_id"), &TileSet::set_source_id); ClassDB::bind_method(D_METHOD("get_source_count"), &TileSet::get_source_count); @@ -2212,6 +2647,27 @@ void TileSet::_bind_methods() { ClassDB::bind_method(D_METHOD("set_custom_data_layers_count", "custom_data_layers_count"), &TileSet::set_custom_data_layers_count); ClassDB::bind_method(D_METHOD("get_custom_data_layers_count"), &TileSet::get_custom_data_layers_count); + // Tile proxies + ClassDB::bind_method(D_METHOD("set_source_level_tile_proxy", "source_from", "source_to"), &TileSet::set_source_level_tile_proxy); + ClassDB::bind_method(D_METHOD("get_source_level_tile_proxy", "source_from"), &TileSet::get_source_level_tile_proxy); + ClassDB::bind_method(D_METHOD("has_source_level_tile_proxy", "source_from"), &TileSet::has_source_level_tile_proxy); + ClassDB::bind_method(D_METHOD("remove_source_level_tile_proxy", "source_from"), &TileSet::remove_source_level_tile_proxy); + + ClassDB::bind_method(D_METHOD("set_coords_level_tile_proxy", "p_source_from", "coords_from", "source_to", "coords_to"), &TileSet::set_coords_level_tile_proxy); + ClassDB::bind_method(D_METHOD("get_coords_level_tile_proxy", "source_from", "coords_from"), &TileSet::get_coords_level_tile_proxy); + ClassDB::bind_method(D_METHOD("has_coords_level_tile_proxy", "source_from", "coords_from"), &TileSet::has_coords_level_tile_proxy); + ClassDB::bind_method(D_METHOD("remove_coords_level_tile_proxy", "source_from", "coords_from"), &TileSet::remove_coords_level_tile_proxy); + + ClassDB::bind_method(D_METHOD("set_alternative_level_tile_proxy", "source_from", "coords_from", "alternative_from", "source_to", "coords_to", "alternative_to"), &TileSet::set_alternative_level_tile_proxy); + ClassDB::bind_method(D_METHOD("get_alternative_level_tile_proxy", "source_from", "coords_from", "alternative_from"), &TileSet::get_alternative_level_tile_proxy); + ClassDB::bind_method(D_METHOD("has_alternative_level_tile_proxy", "source_from", "coords_from", "alternative_from"), &TileSet::has_alternative_level_tile_proxy); + ClassDB::bind_method(D_METHOD("remove_alternative_level_tile_proxy", "source_from", "coords_from", "alternative_from"), &TileSet::remove_alternative_level_tile_proxy); + + ClassDB::bind_method(D_METHOD("map_tile_proxy", "source_from", "coords_from", "alternative_from"), &TileSet::map_tile_proxy); + + ClassDB::bind_method(D_METHOD("cleanup_invalid_tile_proxies"), &TileSet::cleanup_invalid_tile_proxies); + ClassDB::bind_method(D_METHOD("clear_tile_proxies"), &TileSet::clear_tile_proxies); + ADD_GROUP("Rendering", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uv_clipping"), "set_uv_clipping", "is_uv_clipping"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "y_sorting"), "set_y_sorting", "is_y_sorting"); @@ -2755,8 +3211,8 @@ void TileSetAtlasSource::clear_tiles_outside_texture() { } int TileSetAtlasSource::create_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_id_override) { - ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), -1, vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); - ERR_FAIL_COND_V_MSG(p_alternative_id_override >= 0 && tiles[p_atlas_coords].alternatives.has(p_alternative_id_override), -1, vformat("Cannot create alternative tile. Another alternative exists with id %d.", p_alternative_id_override)); + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), TileSetSource::INVALID_TILE_ALTERNATIVE, vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + ERR_FAIL_COND_V_MSG(p_alternative_id_override >= 0 && tiles[p_atlas_coords].alternatives.has(p_alternative_id_override), TileSetSource::INVALID_TILE_ALTERNATIVE, vformat("Cannot create alternative tile. Another alternative exists with id %d.", p_alternative_id_override)); int new_alternative_id = p_alternative_id_override >= 0 ? p_alternative_id_override : tiles[p_atlas_coords].next_alternative_id; @@ -2809,7 +3265,7 @@ bool TileSetAtlasSource::has_alternative_tile(const Vector2i p_atlas_coords, int } int TileSetAtlasSource::get_next_alternative_tile_id(const Vector2i p_atlas_coords) const { - ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), -1, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords))); + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), TileSetSource::INVALID_TILE_ALTERNATIVE, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords))); return tiles[p_atlas_coords].next_alternative_id; } @@ -2819,8 +3275,8 @@ int TileSetAtlasSource::get_alternative_tiles_count(const Vector2i p_atlas_coord } int TileSetAtlasSource::get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const { - ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), -1, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords))); - ERR_FAIL_INDEX_V(p_index, tiles[p_atlas_coords].alternatives_ids.size(), -1); + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), TileSetSource::INVALID_TILE_ALTERNATIVE, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords))); + ERR_FAIL_INDEX_V(p_index, tiles[p_atlas_coords].alternatives_ids.size(), TileSetSource::INVALID_TILE_ALTERNATIVE); return tiles[p_atlas_coords].alternatives_ids[p_index]; } @@ -2861,7 +3317,7 @@ void TileSetAtlasSource::_bind_methods() { ClassDB::bind_method(D_METHOD("get_tile_at_coords", "atlas_coords"), &TileSetAtlasSource::get_tile_at_coords); // Alternative tiles - ClassDB::bind_method(D_METHOD("create_alternative_tile", "atlas_coords", "alternative_id_override"), &TileSetAtlasSource::create_alternative_tile, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("create_alternative_tile", "atlas_coords", "alternative_id_override"), &TileSetAtlasSource::create_alternative_tile, DEFVAL(INVALID_TILE_ALTERNATIVE)); ClassDB::bind_method(D_METHOD("remove_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetAtlasSource::remove_alternative_tile); ClassDB::bind_method(D_METHOD("set_alternative_tile_id", "atlas_coords", "alternative_tile", "new_id"), &TileSetAtlasSource::set_alternative_tile_id); ClassDB::bind_method(D_METHOD("has_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetAtlasSource::has_alternative_tile); @@ -2948,7 +3404,7 @@ bool TileSetScenesCollectionSource::has_alternative_tile(const Vector2i p_atlas_ } int TileSetScenesCollectionSource::create_scene_tile(Ref<PackedScene> p_packed_scene, int p_id_override) { - ERR_FAIL_COND_V_MSG(p_id_override >= 0 && scenes.has(p_id_override), -1, vformat("Cannot create scene tile. Another scene tile exists with id %d.", p_id_override)); + ERR_FAIL_COND_V_MSG(p_id_override >= 0 && scenes.has(p_id_override), INVALID_TILE_ALTERNATIVE, vformat("Cannot create scene tile. Another scene tile exists with id %d.", p_id_override)); int new_scene_id = p_id_override >= 0 ? p_id_override : next_scene_id; @@ -3096,7 +3552,7 @@ void TileSetScenesCollectionSource::_bind_methods() { ClassDB::bind_method(D_METHOD("get_scene_tiles_count"), &TileSetScenesCollectionSource::get_scene_tiles_count); ClassDB::bind_method(D_METHOD("get_scene_tile_id", "index"), &TileSetScenesCollectionSource::get_scene_tile_id); ClassDB::bind_method(D_METHOD("has_scene_tile_id", "id"), &TileSetScenesCollectionSource::has_scene_tile_id); - ClassDB::bind_method(D_METHOD("create_scene_tile", "packed_scene", "id_override"), &TileSetScenesCollectionSource::create_scene_tile, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("create_scene_tile", "packed_scene", "id_override"), &TileSetScenesCollectionSource::create_scene_tile, DEFVAL(INVALID_TILE_ALTERNATIVE)); ClassDB::bind_method(D_METHOD("set_scene_tile_id", "id", "new_id"), &TileSetScenesCollectionSource::set_scene_tile_id); ClassDB::bind_method(D_METHOD("set_scene_tile_scene", "id", "packed_scene"), &TileSetScenesCollectionSource::set_scene_tile_scene); ClassDB::bind_method(D_METHOD("get_scene_tile_scene", "id"), &TileSetScenesCollectionSource::get_scene_tile_scene); @@ -3919,7 +4375,7 @@ void TileSetPluginAtlasRendering::update_dirty_quadrants(TileMap *p_tile_map, Se // Iterate over the cells of the quadrant. for (Map<Vector2i, Vector2i, TileMapQuadrant::CoordsWorldComparator>::Element *E_cell = q.world_to_map.front(); E_cell; E_cell = E_cell->next()) { - TileMapCell c = p_tile_map->get_cell(E_cell->value()); + TileMapCell c = p_tile_map->get_cell(E_cell->value(), true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -4059,7 +4515,7 @@ void TileSetPluginAtlasRendering::draw_quadrant_debug(TileMap *p_tile_map, TileM RenderingServer *rs = RenderingServer::get_singleton(); Vector2 quadrant_pos = p_tile_map->map_to_world(p_quadrant->coords * p_tile_map->get_effective_quadrant_size()); for (Set<Vector2i>::Element *E_cell = p_quadrant->cells.front(); E_cell; E_cell = E_cell->next()) { - const TileMapCell &c = p_tile_map->get_cell(E_cell->get()); + const TileMapCell &c = p_tile_map->get_cell(E_cell->get(), true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -4151,7 +4607,7 @@ void TileSetPluginAtlasPhysics::update_dirty_quadrants(TileMap *p_tile_map, Self } for (Set<Vector2i>::Element *E_cell = q.cells.front(); E_cell; E_cell = E_cell->next()) { - TileMapCell c = p_tile_map->get_cell(E_cell->get()); + TileMapCell c = p_tile_map->get_cell(E_cell->get(), true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -4276,7 +4732,7 @@ void TileSetPluginAtlasPhysics::draw_quadrant_debug(TileMap *p_tile_map, TileMap Color debug_collision_color = p_tile_map->get_tree()->get_debug_collisions_color(); for (Set<Vector2i>::Element *E_cell = p_quadrant->cells.front(); E_cell; E_cell = E_cell->next()) { - TileMapCell c = p_tile_map->get_cell(E_cell->get()); + TileMapCell c = p_tile_map->get_cell(E_cell->get(), true); Transform2D xform; xform.set_origin(p_tile_map->map_to_world(E_cell->get()) - quadrant_pos); @@ -4370,7 +4826,7 @@ void TileSetPluginAtlasNavigation::update_dirty_quadrants(TileMap *p_tile_map, S // Get the navigation polygons and create regions. for (Set<Vector2i>::Element *E_cell = q.cells.front(); E_cell; E_cell = E_cell->next()) { - TileMapCell c = p_tile_map->get_cell(E_cell->get()); + TileMapCell c = p_tile_map->get_cell(E_cell->get(), true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -4455,7 +4911,7 @@ void TileSetPluginAtlasNavigation::draw_quadrant_debug(TileMap *p_tile_map, Tile Vector2 quadrant_pos = p_tile_map->map_to_world(p_quadrant->coords * p_tile_map->get_effective_quadrant_size()); for (Set<Vector2i>::Element *E_cell = p_quadrant->cells.front(); E_cell; E_cell = E_cell->next()) { - TileMapCell c = p_tile_map->get_cell(E_cell->get()); + TileMapCell c = p_tile_map->get_cell(E_cell->get(), true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -4526,7 +4982,7 @@ void TileSetPluginScenesCollections::update_dirty_quadrants(TileMap *p_tile_map, // Recreate the scenes. for (Set<Vector2i>::Element *E_cell = q.cells.front(); E_cell; E_cell = E_cell->next()) { - const TileMapCell &c = p_tile_map->get_cell(E_cell->get()); + const TileMapCell &c = p_tile_map->get_cell(E_cell->get(), true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -4585,7 +5041,7 @@ void TileSetPluginScenesCollections::draw_quadrant_debug(TileMap *p_tile_map, Ti RenderingServer *rs = RenderingServer::get_singleton(); Vector2 quadrant_pos = p_tile_map->map_to_world(p_quadrant->coords * p_tile_map->get_effective_quadrant_size()); for (Set<Vector2i>::Element *E_cell = p_quadrant->cells.front(); E_cell; E_cell = E_cell->next()) { - const TileMapCell &c = p_tile_map->get_cell(E_cell->get()); + const TileMapCell &c = p_tile_map->get_cell(E_cell->get(), true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h index dbf6dbabe6..0a07981171 100644 --- a/scene/resources/tile_set.h +++ b/scene/resources/tile_set.h @@ -79,15 +79,15 @@ private: Vector2 tex_offset; Ref<ShaderMaterial> material; Rect2 region; - int tile_mode; - Color modulate; + int tile_mode = 0; + Color modulate = Color(1, 1, 1); // Atlas or autotiles data - int autotile_bitmask_mode; + int autotile_bitmask_mode = 0; Vector2 autotile_icon_coordinate; Size2i autotile_tile_size = Size2i(16, 16); - int autotile_spacing; + int autotile_spacing = 0; Map<Vector2i, int> autotile_bitmask_flags; Map<Vector2i, Ref<OccluderPolygon2D>> autotile_occluder_map; Map<Vector2i, Ref<NavigationPolygon>> autotile_navpoly_map; @@ -99,23 +99,29 @@ private: Vector2 occluder_offset; Ref<NavigationPolygon> navigation; Vector2 navigation_offset; - int z_index; + int z_index = 0; }; - Map<int, CompatibilityTileData *> compatibility_data = Map<int, CompatibilityTileData *>(); - Map<int, int> compatibility_source_mapping = Map<int, int>(); + enum CompatibilityTileMode { + COMPATIBILITY_TILE_MODE_SINGLE_TILE = 0, + COMPATIBILITY_TILE_MODE_AUTO_TILE, + COMPATIBILITY_TILE_MODE_ATLAS_TILE, + }; -private: - void compatibility_conversion(); + Map<int, CompatibilityTileData *> compatibility_data; + Map<int, int> compatibility_tilemap_mapping_tile_modes; + Map<int, Map<Array, Array>> compatibility_tilemap_mapping; -public: - int compatibility_get_source_for_tile_id(int p_old_source) { - return compatibility_source_mapping[p_old_source]; - }; + void _compatibility_conversion(); +public: + // Format of output array [source_id, atlas_coords, alternative] + Array compatibility_tilemap_map(int p_tile_id, Vector2i p_coords, bool p_flip_h, bool p_flip_v, bool p_transpose); #endif // DISABLE_DEPRECATED public: + static const int INVALID_SOURCE; // -1; + enum CellNeighbor { CELL_NEIGHBOR_RIGHT_SIDE = 0, CELL_NEIGHBOR_RIGHT_CORNER, @@ -165,7 +171,6 @@ public: TILE_OFFSET_AXIS_VERTICAL, }; -public: struct PackedSceneSource { Ref<PackedScene> scene; Vector2 offset; @@ -246,6 +251,11 @@ private: void _compute_next_source_id(); void _source_changed(); + // Tile proxies + Map<int, int> source_level_proxies; + Map<Array, Array> coords_level_proxies; + Map<Array, Array> alternative_level_proxies; + // Helpers Vector<Point2> _get_square_corner_or_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); Vector<Point2> _get_square_corner_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); @@ -341,6 +351,31 @@ public: void set_custom_data_type(int p_layer_id, Variant::Type p_value); Variant::Type get_custom_data_type(int p_layer_id) const; + // Tiles proxies. + void set_source_level_tile_proxy(int p_source_from, int p_source_to); + int get_source_level_tile_proxy(int p_source_from); + bool has_source_level_tile_proxy(int p_source_from); + void remove_source_level_tile_proxy(int p_source_from); + + void set_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_source_to, Vector2i p_coords_to); + Array get_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from); + bool has_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from); + void remove_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from); + + void set_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from, int p_source_to, Vector2i p_coords_to, int p_alternative_to); + Array get_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from); + bool has_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from); + void remove_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from); + + Array get_source_level_tile_proxies() const; + Array get_coords_level_tile_proxies() const; + Array get_alternative_level_tile_proxies() const; + + Array map_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from) const; + + void cleanup_invalid_tile_proxies(); + void clear_tile_proxies(); + // Helpers Vector<Vector2> get_tile_shape_polygon(); void draw_tile_shape(CanvasItem *p_canvas_item, Rect2 p_region, Color p_color, bool p_filled = false, Ref<Texture2D> p_texture = Ref<Texture2D>()); diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 0f6412e6e9..ef380ab233 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -1740,6 +1740,7 @@ void VisualShader::_update_shader() const { } Map<int, String> code_map; + Set<int> empty_funcs; for (int i = 0; i < TYPE_MAX; i++) { if (!has_func_name(RenderingServer::ShaderMode(shader_mode), func_name[i])) { @@ -1776,6 +1777,7 @@ void VisualShader::_update_shader() const { } if (is_empty_func) { + empty_funcs.insert(i); continue; } @@ -1934,7 +1936,11 @@ void VisualShader::_update_shader() const { if (!has_func_name(RenderingServer::ShaderMode(shader_mode), func_name[i])) { continue; } - tcode = tcode.insert(insertion_pos[i], global_code_per_func[Type(i)]); + String func_code = global_code_per_func[Type(i)].as_string(); + if (empty_funcs.has(Type(i)) && !func_code.is_empty()) { + func_code = vformat("%s%s%s", String("\nvoid " + String(func_name[i]) + "() {\n"), func_code, "}\n"); + } + tcode = tcode.insert(insertion_pos[i], func_code); } final_code += tcode; diff --git a/servers/display_server.cpp b/servers/display_server.cpp index be2a813fd1..3d44484033 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -346,8 +346,6 @@ void DisplayServer::_bind_methods() { ClassDB::bind_method(D_METHOD("global_menu_remove_item", "menu_root", "idx"), &DisplayServer::global_menu_remove_item); ClassDB::bind_method(D_METHOD("global_menu_clear", "menu_root"), &DisplayServer::global_menu_clear); - ClassDB::bind_method(D_METHOD("alert", "text", "title"), &DisplayServer::alert, DEFVAL("Alert!")); - ClassDB::bind_method(D_METHOD("mouse_set_mode", "mouse_mode"), &DisplayServer::mouse_set_mode); ClassDB::bind_method(D_METHOD("mouse_get_mode"), &DisplayServer::mouse_get_mode); diff --git a/servers/display_server.h b/servers/display_server.h index 8d289b10fd..788206768c 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -143,8 +143,6 @@ public: virtual void global_menu_remove_item(const String &p_menu_root, int p_idx); virtual void global_menu_clear(const String &p_menu_root); - virtual void alert(const String &p_alert, const String &p_title = "ALERT!") = 0; - enum MouseMode { MOUSE_MODE_VISIBLE, MOUSE_MODE_HIDDEN, diff --git a/servers/display_server_headless.h b/servers/display_server_headless.h index 870401b180..d9ee91084f 100644 --- a/servers/display_server_headless.h +++ b/servers/display_server_headless.h @@ -55,8 +55,6 @@ public: bool has_feature(Feature p_feature) const override { return false; } String get_name() const override { return "headless"; } - void alert(const String &p_alert, const String &p_title = "ALERT!") override {} - int get_screen_count() const override { return 0; } Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override { return Point2i(); } Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override { return Size2i(); } diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp index f771d6d0ef..3b9aca200b 100644 --- a/servers/rendering/shader_language.cpp +++ b/servers/rendering/shader_language.cpp @@ -946,7 +946,9 @@ void ShaderLanguage::_parse_used_identifier(const StringName &p_identifier, Iden break; case IdentifierType::IDENTIFIER_VARYING: if (HAS_WARNING(ShaderWarning::UNUSED_VARYING_FLAG) && used_varyings.has(p_identifier)) { - used_varyings[p_identifier].used = true; + if (shader->varyings[p_identifier].stage != ShaderNode::Varying::STAGE_VERTEX && shader->varyings[p_identifier].stage != ShaderNode::Varying::STAGE_FRAGMENT) { + used_varyings[p_identifier].used = true; + } } break; case IdentifierType::IDENTIFIER_UNIFORM: @@ -2434,6 +2436,10 @@ bool ShaderLanguage::_validate_function_call(BlockNode *p_block, const FunctionI if (shader->uniforms.has(varname)) { fail = true; } else { + if (shader->varyings.has(varname)) { + _set_error(vformat("Varyings cannot be passed for '%s' parameter!", "out")); + return false; + } if (p_function_info.built_ins.has(varname)) { BuiltInInfo info = p_function_info.built_ins[varname]; if (info.constant) { @@ -3306,8 +3312,8 @@ bool ShaderLanguage::_is_operator_assign(Operator p_op) const { } bool ShaderLanguage::_validate_varying_assign(ShaderNode::Varying &p_varying, String *r_message) { - if (current_function == String("light")) { - *r_message = RTR("Varying may not be assigned in the 'light' function."); + if (current_function != String("vertex") && current_function != String("fragment")) { + *r_message = vformat(RTR("Varying may not be assigned in the '%s' function."), current_function); return false; } switch (p_varying.stage) { @@ -3318,12 +3324,15 @@ bool ShaderLanguage::_validate_varying_assign(ShaderNode::Varying &p_varying, St p_varying.stage = ShaderNode::Varying::STAGE_FRAGMENT; } break; + case ShaderNode::Varying::STAGE_VERTEX_TO_FRAGMENT: + case ShaderNode::Varying::STAGE_VERTEX_TO_LIGHT: case ShaderNode::Varying::STAGE_VERTEX: if (current_function == varying_function_names.fragment) { *r_message = RTR("Varyings which assigned in 'vertex' function may not be reassigned in 'fragment' or 'light'."); return false; } break; + case ShaderNode::Varying::STAGE_FRAGMENT_TO_LIGHT: case ShaderNode::Varying::STAGE_FRAGMENT: if (current_function == varying_function_names.vertex) { *r_message = RTR("Varyings which assigned in 'fragment' function may not be reassigned in 'vertex' or 'light'."); @@ -4114,6 +4123,10 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons } else if (shader->uniforms.has(varname)) { error = true; } else { + if (shader->varyings.has(varname)) { + _set_error(vformat("Varyings cannot be passed for '%s' parameter!", _get_qualifier_str(call_function->arguments[i].qualifier))); + return nullptr; + } if (p_function_info.built_ins.has(varname)) { BuiltInInfo info = p_function_info.built_ins[varname]; if (info.constant) { @@ -7862,15 +7875,6 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct tk = _get_token(); } - - for (Map<StringName, ShaderNode::Varying>::Element *E = shader->varyings.front(); E; E = E->next()) { - if (E->get().stage == ShaderNode::Varying::STAGE_VERTEX || E->get().stage == ShaderNode::Varying::STAGE_FRAGMENT) { - _set_tkpos(E->get().tkpos); - _set_error(RTR("Varying must only be used in two different stages, which can be 'vertex' 'fragment' and 'light'")); - return ERR_PARSE_ERROR; - } - } - return OK; } diff --git a/tests/test_object.h b/tests/test_object.h index 36f9ef2a51..a18adf31b6 100644 --- a/tests/test_object.h +++ b/tests/test_object.h @@ -139,7 +139,7 @@ TEST_CASE("[Object] Metadata") { Color(object.get_meta(meta_path)).is_equal_approx(Color(0, 1, 0)), "The returned object metadata after setting should match the expected value."); - List<String> meta_list; + List<StringName> meta_list; object.get_meta_list(&meta_list); CHECK_MESSAGE( meta_list.size() == 1, @@ -154,7 +154,7 @@ TEST_CASE("[Object] Metadata") { "The returned object metadata after removing should match the expected value."); ERR_PRINT_ON; - List<String> meta_list2; + List<StringName> meta_list2; object.get_meta_list(&meta_list2); CHECK_MESSAGE( meta_list2.size() == 0, |